/*
Eteria IRC Client, an RFC 1459 compliant client program written in Java.
Copyright (C) 2000-2001  Javier Kohen <jkohen at tough.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

import java.applet.AudioClip;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.lang.reflect.*;
import java.net.*;
import java.text.*;
import java.util.*;

import ar.com.jkohen.applet.*;
import ar.com.jkohen.awt.ChatPanel;
import ar.com.jkohen.awt.ImageCanvas;
import ar.com.jkohen.awt.NickInfo;
import ar.com.jkohen.awt.event.ChatPanelEvent;
import ar.com.jkohen.awt.event.ChatPanelListener;
import ar.com.jkohen.awt.MircSmileyTextArea;
import ar.com.jkohen.awt.TextAttributePicker;
import ar.com.jkohen.irc.*;
import ar.com.jkohen.net.*;
import ar.com.jkohen.util.*;

/**
 * This is Eteria IRC Client's core class. It contains the main control loop, some high level communication methods and topmost-GUI code.
 *
 * @author Javier Kohen
 */

public class EIRC extends Applet implements ClientProcess, Observer, ActionListener, ItemListener, MouseListener, ChatPanelListener, WindowListener
{
    /**
     * Program's name.
     * Used as part of "About" info.
     */

    public final static String PACKAGE = "Eteria IRC Client";

	/**
     * Program's release version.
     * Used as part of "About" info.
     * Should be either a "YYYYMMDD" date or a "X.Y.Z" version number.
     */

    public final static String VERSION = "Beta 20081102 10:30";

    /**
     * Program's release extra version.
     * Used as part of "About" info.
     * Should be a String describing in-site's modifications.
     */

    public final static String VERSION_EXTRA = "Version www.coolsmile.net + avatars";

    /**
     * Author's name.
     * Used as part of "About" info.
     */

    public final static String AUTHOR = "Javier Kohen";

    private static Hashtable commands;
    private static ResourceBundle user_commands;
    private static Resources res;

    private Collator collator;
    private Vector channels;
    private CollatedHashtable channel_windows;
    private CollatedHashtable privates;
	private Vector ignore_list;
	private Vector names;

//    private boolean missing_default_channel_in_list;

	// Used to build the users list from the server replies to the WHO command.
	private Vector who_reply;

    // Store a channel's +b/e/I list
    private Vector list;

    private ConfigurationProperties properties;

	private static Font mainfont = new Font("Helvetica", Font.PLAIN, 11);
    private static Color mainbg = SystemColor.control;
    private static Color mainfg = SystemColor.controlText;
    private static Color textbg = SystemColor.window;
    private static Color textfg = SystemColor.textText;
    private static Color selbg = SystemColor.textHighlight;
    private static Color selfg = SystemColor.textHighlightText;

    /* GUI. */
    private Image main_icon;
    private TextField nick_entry;
	private Label label;
    private Label away;
	private PopupMenu menu;
    private ControlPanel control_panel;
    private ChatPanelContainer chat_panel;
    private StatusWindow status;
    private ChanListWindow chan_list;
	private WhoListWindow who_list;
	private OutputWindow debug_window;
	private Frame f;
	private boolean application = false;
	private Hashtable color_list;

	private String current_server;
	private String status_tag;
    private String current_nick, username, realname;
    private String new_nick;
    private String original_nick;
    private ServerProcess server;
    private IRCServices irc_services;
	private boolean is_away;
	private String away_str;

    /* Configuration properties. */
    private boolean special_services;
    private int debug_traffic;
    private String nicksrv_pass;
    private String irc_pass;
    private String quit_message;
    private boolean request_motd;
    private boolean see_everything_from_server;
    private boolean see_join;
	private boolean see_invite;
    private boolean on_dcc_notify_peer;
    private String service_bots;
    private boolean hideip;
    private boolean focus_opening_privates;
	private boolean no_privates;
	private boolean auto_list;
	private String user_modes;
	private int write_color;
	private int scroll_speed;
	private int silent;
	private AudioClip event_sounds[];
	private String net_encoding;

	private String network_name = "";
    private String default_join;
	private int restrict_join;
    private boolean connected;
    private boolean logged_in;
    private boolean quit_sent;
	private boolean server_admin;
	private boolean ircop_override;
	private boolean who_invisible;
	private boolean login_time;

    // Dialogs.
	private static Configurator configurator;
    private static ModeList mode_list;
	private static TextAreaCopy tac;
	private static ASLBox asl;
	private static HelpBox help;

    private MessageFormat chan_title, private_title, chanlist_title, wholist_title;

    static
	{
        commands = new Hashtable(50, 1);
		commands.put("ping", new Integer(-1));
		commands.put("nick", new Integer(-2));
		commands.put("join", new Integer(-3));
		commands.put("mode", new Integer(-4));
		commands.put("part", new Integer(-5));
		commands.put("quit", new Integer(-6));
		commands.put("kick", new Integer(-7));
		commands.put("topic", new Integer(-8));
		commands.put("privmsg", new Integer(-9));
		commands.put("notice", new Integer(-10));
		commands.put("error", new Integer(-11));
		commands.put("wallops", new Integer(-12));
		commands.put("invite", new Integer(-13));
		commands.put("pong", new Integer(-14));
		commands.put("001", new Integer(001));
		commands.put("232", new Integer(232));
		commands.put("251", new Integer(251));
		commands.put("301", new Integer(301));
		commands.put("303", new Integer(303));
		commands.put("302", new Integer(302));
		commands.put("305", new Integer(305));
		commands.put("306", new Integer(306));
		commands.put("307", new Integer(307));
  		commands.put("310", new Integer(310));
  		commands.put("311", new Integer(311));
		commands.put("312", new Integer(312));
  		commands.put("313", new Integer(313));
  		commands.put("314", new Integer(314));
  		commands.put("315", new Integer(315));
  		commands.put("317", new Integer(317));
		commands.put("318", new Integer(318)); // No action needed.
		commands.put("319", new Integer(319));
  		commands.put("320", new Integer(320));
  		commands.put("321", new Integer(321));
		commands.put("322", new Integer(322));
  		commands.put("323", new Integer(323));
  		commands.put("324", new Integer(324));
		commands.put("329", new Integer(329)); // No action needed.
		commands.put("331", new Integer(331));
		commands.put("332", new Integer(332));
  		commands.put("333", new Integer(333));
  		commands.put("335", new Integer(335));
		commands.put("346", new Integer(346));
		commands.put("347", new Integer(347));
		commands.put("348", new Integer(348));
  		commands.put("349", new Integer(349));
	  	commands.put("352", new Integer(352));
  		commands.put("353", new Integer(353));
		commands.put("366", new Integer(366));
		commands.put("367", new Integer(367));
		commands.put("368", new Integer(368));
		commands.put("371", new Integer(371));
		commands.put("372", new Integer(372));
		commands.put("375", new Integer(375));
    	commands.put("376", new Integer(376));
		commands.put("378", new Integer(378));
		commands.put("381", new Integer(381));
		commands.put("391", new Integer(391));
    	commands.put("401", new Integer(401));
    	commands.put("404", new Integer(404));
    	commands.put("406", new Integer(406));
		commands.put("421", new Integer(421));
		commands.put("422", new Integer(422));
	  	commands.put("432", new Integer(432));
  		commands.put("433", new Integer(433));
		commands.put("437", new Integer(437));
  		commands.put("438", new Integer(438));
		commands.put("447", new Integer(447));
  		commands.put("461", new Integer(461));
		commands.put("464", new Integer(464));
		commands.put("465", new Integer(465));
  		commands.put("471", new Integer(471));
  		commands.put("473", new Integer(473));
	  	commands.put("474", new Integer(474));
	  	commands.put("475", new Integer(475));
		commands.put("477", new Integer(477));
		commands.put("478", new Integer(478));
	  	commands.put("481", new Integer(481));
	  	commands.put("482", new Integer(482));
	  	commands.put("491", new Integer(491));
		commands.put("536", new Integer(536));
	  	commands.put("600", new Integer(600));
	  	commands.put("601", new Integer(601));
	  	commands.put("604", new Integer(604));
	  	commands.put("605", new Integer(605));
    }

    /**
     * Constructor for Applet instances.
     * Must be present, even if empty.
     */

	public EIRC()
	{
	}

	public EIRC(boolean b)
	{
		this.application = b;
	}

    /**
     * Execution starts here when the program is run out of Applet context.
     * This method supplies a simple substitute for the Applet viewer.
     *
     * @param args a <code>String[]</code> containing command-line arguments.
     */

    public static void main(String args[])
	{
		EIRC eirc = new EIRC(true);
		eirc.setStub(new SimpleAppletStub(args));
		eirc.init();
		eirc.start();
	}

    public void init()
	{
		this.collator = RFC1459.getCollator();

	  	this.channels = new Vector();
		this.channel_windows = new CollatedHashtable(collator);
		this.privates = new CollatedHashtable(collator);
		this.ignore_list = new Vector();
		this.names = new Vector();
		this.event_sounds = new AudioClip[res.EVENTS];

		res = new Resources(this, getParameter("language"));

		/* Get main configuration
		*/
		Properties default_props = new Properties();
		try
		{
	    	default_props.load(getClass().getResourceAsStream("configuration.properties"));
	    	this.properties = new ConfigurationProperties(default_props);
		} catch (IOException ex)
		{
	    	System.err.println(ex);
	    	this.properties = new ConfigurationProperties();
		}
		properties.addObserver(this);


		/* Read user's colors and sounds configurations
		*/
		Vector sound_list = new Vector();
		color_list = new Hashtable();
		for (Enumeration e = default_props.keys(); e.hasMoreElements() ;)
		{
			String key = (String)e.nextElement();
			try
			{
				if (key.indexOf("sound.") == 0 && Integer.parseInt(key.substring(6)) > 0)
					sound_list.addElement(default_props.getProperty(key));
				if (key.indexOf("color.") == 0)
					color_list.put(key.substring(6), Color.decode(default_props.getProperty(key)));
			}
			catch(NumberFormatException ex) {}
		}

		/* Load sounds in background
		*/
		if (properties.getBoolean("load_sounds"))
		{
			res.addObserver(this);
			res.cacheSounds(sound_list, default_props.getProperty("sound.path"));
		}

		/* Force an update to initialize some variables to their defaults.
		*/
		update(properties, null);


//		EIRC.user_commands = ResourceBundle.getBundle("Commands");
		EIRC.user_commands = new Commands();

		/* Read text colors
		*/
		Color fixedColors[] = new Color[16]; // 16 colors + 2 (fg/bg) internal.
		for (int i = 0; i < fixedColors.length; i++)
		{
			try
			{
				fixedColors[i] = Color.decode(properties.getString("n" + i));
			}
			catch (Exception ex)
			{
				fixedColors[i] = Color.black;
				System.err.println(ex);
			}
		}
		MircSmileyTextArea.setColors(fixedColors);
		TextAttributePicker.setColors(fixedColors);
		int order[] = new int[16];
		try
		{
			for (int i = 0; i < order.length; i++)
				order[i] = Integer.parseInt(properties.getString("c" + i));
		}
		catch (Exception ex)
		{
			for (int i = 0; i < order.length; i++)
				order[i] = i;
		}
		TextAttributePicker.setOrder(order);


		for (Enumeration e = Resources.lang_resource.propertyNames(); e.hasMoreElements() ;)
		{
			String key = (String)e.nextElement();
			if (key.indexOf("tld.") == 0)
				NickInfo.addTLD(key.substring(key.indexOf(".") + 1), res.getString(key));
		}
		try
		{
			NickInfo.setParseASLType(Integer.parseInt(properties.getString("default_asl")));
		} catch (NumberFormatException e) {}


		chan_title = new MessageFormat(res.getString("top_panel.chan"));
		private_title = new MessageFormat(res.getString("top_panel.private"));
		chanlist_title = new MessageFormat(res.getString("top_panel.chanlist"));
		wholist_title = new MessageFormat(res.getString("top_panel.wholist"));
		main_icon = res.getImage("icon");

		irc_services = new IRCServices(this);

		SimpleAppletContext.setPath(default_props.getProperty("navigator"));

		this.default_join = getParameter("join");


		String t, n = "SansSerif";
		int s = 12;

		if ((t = getParameter("font_name")) != null)
			n = t;
			
		try
		{
			if ((t = getParameter("font_size")) != null)
		    	s = Integer.parseInt(t);
		}
		catch (NumberFormatException e)	{}
	    this.mainfont = new Font(n, Font.PLAIN, s);

		try
		{
			if ((t = getParameter("mainbg")) != null)
	    		this.mainbg = Color.decode(t);
		}
		catch (NumberFormatException e)	{}
		try
		{
			if ((t = getParameter("mainfg")) != null)
		    	this.mainfg = Color.decode(t);
		}
		catch (NumberFormatException e)	{}
		try
		{
			if ((t = getParameter("textbg")) != null)
		    	this.textbg = Color.decode(t);
		}
		catch (NumberFormatException e)	{}
		try
		{
			if ((t = getParameter("textfg")) != null)
			    this.textfg = Color.decode(t);
		}
		catch (NumberFormatException e)	{}
		try
		{
			if ((t = getParameter("selbg")) != null)
			    this.selbg = Color.decode(t);
		}
		catch (NumberFormatException e)	{}
		try
		{
			if ((t = getParameter("selfg")) != null)
			    this.selfg = Color.decode(t);
		}
		catch (NumberFormatException e)	{}


		// Send traffic to stderr.
		// Turn off for production setups!
		debug_traffic = 0;
		if ((t = getParameter("debug_traffic")) != null)
		{
			try
			{
		    	debug_traffic = Integer.parseInt(t);
			} catch (NumberFormatException e) {}
		}

		write_color = 1;
		if ((t = getParameter("write_color")) != null)
		{
			try
			{
	    		write_color = Integer.parseInt(t);
	    		if (write_color < 0 || write_color > 15)
	    			write_color = 1;
			} catch (NumberFormatException e) {}
			properties.setInt("write_color", write_color);
		}

		if ((t = getParameter("nicksrv_pass")) == null)
		    t = "";
		properties.setString("nicksrv_pass", t);

		if ((t = getParameter("user_modes")) != null)
		{
			user_modes = "";
			for (int i = 0; i < t.length(); i++)
			{
				char c = t.charAt(i);
				if (Character.isLetter(c) || c == '+' || c == '-')
					user_modes += c;
			}
		}
		
    	/* Create main GUI. */

//		try
//		{
//			if (Class.forName("com.ms.security.PolicyEngine") != null)
//	    		com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.UI);
//		}
//		catch (Exception ex) {}

    	t = getParameter("spawn_frame");

    	if ((t != null && !t.equals("0")) || application)
    	{
    	    f = new Frame(makeWindowTitle(""));
    	    f.setIconImage(main_icon);
    	    f.addWindowListener(this);

    	    int width = 600;
    	    int height = 400;
    	    try
    		{
    			width = Integer.parseInt(getParameter("width"));
    			height = Integer.parseInt(getParameter("height"));
    	    }
			catch (Exception ex) {}
    	    initGUI(f);
	  	    f.pack();
    	    f.setSize(width, height);
    	    Toolkit tkit = getToolkit();
   			f.setLocation((tkit.getScreenSize().width - f.getSize().width) / 2, (tkit.getScreenSize().height - f.getSize().height) / 2);
    	    f.show();
			f.addWindowListener(this);
			f.setName("main");
		}
		else
		{
    		f = null;
			initGUI(this);
		}

		setBackground(mainbg);
		setForeground(mainfg);
		setSelectedBackground(selbg);
		setFont(mainfont);


    	/* Event listeners and observers. */

    	nick_entry.addActionListener(this);
		away.addMouseListener(this);
    	properties.addObserver(chan_list);
		properties.addObserver(who_list);
		properties.addObserver(status);
		properties.addObserver(irc_services);
    	chan_list.update(properties, null);
    	who_list.update(properties, null);
    	status.update(properties, null);
    	irc_services.update(properties, null);


    	/* Misc. GUI init. */

    	if ((t = getParameter("nickname")) != null)
		{
    	    char [] str = t.toCharArray();

    	    // Replace every '?' occurrence by a random decimal digit.
    	    for (int i = 0; i < str.length; i++)
    			if ('?' == str[i])
				   	str[i] = Character.forDigit((int) Math.floor(Math.random() * 10.0), 10);

    	    nick_entry.setText(String.valueOf(str));
    	    original_nick = String.valueOf(str);
    	}


		if ((t = getParameter("irc_pass")) != null && t.length() > 0)
		    properties.setString("irc_pass", t);

		username = "cool";
		realname = "smile";
		
		if ((t = getParameter("username")) != null && t.length() > 0)
	    	username = t;

    	if ((t = getParameter("realname")) != null && t.length() > 0)
    	    realname = t;

		if (debug_traffic > 0)
		{
			String propertyNames[] = { "file.separator", "line.separator", "path.separator", "java.class.version", "java.vendor", "java.vendor.url", "java.version", "os.name", "os.arch", "os.version" };
			debug(getAppletInfo());
			debug("\n");
			for (int i = 0; i < propertyNames.length; i++)
				debug(propertyNames[i] + " = " + System.getProperty(propertyNames[i]));
			debug("\n");
			Locale l = Locale.getDefault();
			debug("country = " + l.getCountry() + " (" + l.getDisplayCountry() + ")");
			debug("language = " + l.getLanguage() + " (" + l.getDisplayLanguage() + ")");
			debug("encoding = " + new java.io.OutputStreamWriter(System.out).getEncoding());
			debug("\n");
		}
    }

	public void openASL()
	{
		String t = getParameter("asl");
		
		if((t != null && !t.equals("0")) || t == null)
		{
			if (asl == null)
			{
				asl = new ASLBox(this, getFrame(), res.getString("asl.connection"));
				asl.addWindowListener(this);
				asl.setName("asl");
			}
			else
			{
				asl.toFront();
			}
			asl.init(nick_entry.getText(), nicksrv_pass, realname);
		}
	}

    public void start()
	{
		String t = getParameter("login");

		// See if auto-login was requested.
		if (t != null && !t.equals("0"))
		{
	    	// Can't login if no nickname was provided.
	    	if ((t = getParameter("nickname")) == null || t.length() == 0)
				status.printError(res.getString("eirc.s26"));
			else
				connect();
		}
		else
		{
			openASL();
		}
    }

    public void stop()
	{
		disconnect();
		closePrivates();
    }

    public void destroy()
	{
		// Remove all components from container.
		removeAll();
    }

    private void initGUI(Container cont)
	{
		String t;
		
		GridBagLayout gb = new GridBagLayout();
		GridBagConstraints gbc = new GridBagConstraints();

		gbc.insets = new Insets(4, 4, 4, 4);
    	cont.setLayout(gb);

    	label = new Label(res.getString("eirc.enter_nick"), Label.RIGHT);
    	gbc.anchor = GridBagConstraints.EAST;
    	gb.setConstraints(label, gbc);
    	label.setBackground(mainbg);
		t = getParameter("gui_nick");
		if (t == null || !t.equals("0"))
	    	cont.add(label);

		this.nick_entry = new TextField(9);
    	gbc.anchor = GridBagConstraints.WEST;
    	gb.setConstraints(nick_entry, gbc);
    	nick_entry.setBackground(textbg);
		if (t == null || !t.equals("0"))
    		cont.add(nick_entry);

		this.menu = new PopupMenu();
		CheckboxMenuItem cbm = new CheckboxMenuItem(res.getString("eirc.away_custom"));
		cbm.addItemListener(this);
		menu.add(cbm);
		menu.addSeparator();
		StringTokenizer tk = new StringTokenizer(res.getString("eirc.away_list"), "/");
    	while (tk.hasMoreTokens())
    	{
    		cbm = new CheckboxMenuItem(tk.nextToken());
    		cbm.addItemListener(this);
    		menu.add(cbm);
    	}

    	away = new Label(res.getString("eirc.away"));
    	gbc.anchor = GridBagConstraints.EAST;
    	gb.setConstraints(away, gbc);
		t = getParameter("gui_away");
		if (t == null || !t.equals("0"))
	    	cont.add(away);
    	away.add(menu);
    	away.setBackground(mainbg);
    	away.setForeground(Color.blue);

		/*
		** Check if we can add some buttons.
		*/
		boolean enabled[] = new boolean[ControlPanel.COMPONENTS];
		for (int i = 0; i < ControlPanel.COMPONENTS; i++)
			enabled[i] = true;
		
		t = getParameter("gui_chanlist");
    	if (t != null && t.equals("0"))
				enabled[ControlPanel.CHANNELS] = false;
		t = getParameter("gui_userlist");
    	if (t != null && t.equals("0"))
			enabled[ControlPanel.WHO] = false;
		t = getParameter("gui_options");
    	if (t != null && t.equals("0"))
			enabled[ControlPanel.SETUP] = false;
		t = getParameter("gui_help");
    	if (t != null && t.equals("0"))
			enabled[ControlPanel.HELP] = false;
		t = getParameter("gui_connect");
    	if (t != null && t.equals("0"))
			enabled[ControlPanel.CONNECT] = false;
		
    	this.control_panel = new ControlPanel(this, enabled);
    	gbc.anchor = GridBagConstraints.EAST;
    	gb.setConstraints(control_panel, gbc);
    	cont.add(control_panel);
    	gbc.anchor = GridBagConstraints.CENTER;

    	gbc.gridy = 1;

    	this.chat_panel = new ChatPanelContainer(this);
    	gbc.weightx = 1.0;
    	gbc.weighty = 1.0;
    	gbc.gridwidth = GridBagConstraints.REMAINDER;
    	gbc.fill = GridBagConstraints.BOTH;
    	gb.setConstraints(chat_panel, gbc);
    	cont.add(chat_panel);

    	/* Create and add Channel list window. */
    	String tag = "*Chans*";

    	this.chan_list = new ChanListWindow(this, tag);
		chan_list.setForeground(mainfg);
		chan_list.setBackground(mainbg);
	    chan_list.setTextBackground(textbg);
	    chan_list.setTextForeground(textfg);
// 	    chan_list.setSelectedForeground(selfg);
 	    chan_list.setSelectedBackground(selbg);
    	chat_panel.addPanel(chan_list, tag);

    	/* Create and add Who list window. */
    	tag = "*Who*";

    	this.who_list = new WhoListWindow(this, tag);
		who_list.setForeground(mainfg);
		who_list.setBackground(mainbg);
	    who_list.setTextBackground(textbg);
//	    who_list.setTextForeground(textfg);
 	    who_list.setSelectedForeground(selfg);
	    who_list.setSelectedBackground(selbg);
    	chat_panel.addPanel(who_list, tag);

    	/* Create and add StatusWindow. */
    	status_tag = Resources.getString("eirc.status");
    	String title = Resources.getString("status_panel");

    	this.status = new StatusWindow(this, status_tag);
    	status.setBackground(mainbg);
    	status.setForeground(mainfg);
    	status.setTextBackground(textbg);
    	status.setTextForeground(textfg);
		status.setSelectedBackground(selbg);
       	status.requestFocus();

      	chat_panel.add(status, status_tag, true);
      	chat_panel.setTitle(status_tag, title);
    	chat_panel.show(status_tag);

		cont.setBackground(mainbg);
		cont.setForeground(mainfg);
		cont.setFont(mainfont);
    }

    public String getAppletInfo()
	{
		return (PACKAGE + " " + VERSION + "\n" + VERSION_EXTRA + ", an RFC 1459 compliant client program written in Java.\n" + "Copyright (C) 2000-2001  Javier Kohen <jkohen at tough.com>");
    }

    public String[][] getParameterInfo()
	{
		return param_info;
    }

    void connect()
	{
		String host = null;
		if (current_server != null)
			host = current_server;
		else
			host = getParameter("server");
		if (host == null || host.length() == 0)
		{
	    	host = getDocumentBase().getHost();
	    	if (host.length() == 0)
				host = "localhost";
		}
		connect(host);
    }

    void connect(String host)
	{
		int port = 6667; // Set default port.
		try
		{
	    	port = Integer.parseInt(getParameter("port"));
		} catch (Exception e) {}

		connect(host, port);
    }

    synchronized void connect(String host, int port)
	{
		if (nick_entry.getText().length() == 0)
		{
	    	status.printError(res.getString("eirc.s1"));
	    	return;
		}

		status.printInfo(res.getString("eirc.login"));
		
		/* See if this Applet can connect promiscuously.
		 * No Exceptions will be thrown out of Applet context.
		 */

		InetAddress server_addr = null;
		Socket s = null;
		ServerThread st = null;

		try
		{
			if (Class.forName("netscape.security.PrivilegeManager") != null)
				netscape.security.PrivilegeManager.enablePrivilege("UniversalConnect");

			if (System.getProperty("java.vendor").indexOf("Microsoft") >= 0)
			{
				if (Class.forName("com.ms.security.PolicyEngine") != null)
					com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.NETIO);
			}

			/* Resolve server's address. */
			InetAddress [] addresses = InetAddress.getAllByName(host);
			server_addr = addresses[(int) Math.floor(Math.random() * addresses.length)];
			
	    	/* Establish a connection to the server. */
			s = createSocket(server_addr, port);
			
	    	/* Spawn a new thread to handle the connection. */
			st = new ServerThread(this, s, net_encoding);
		}
		catch (UnknownHostException e)
		{
    	    Object a[] = { host };
    	    String ptn = res.getString("eirc.s2");
    	    status.printError(MessageFormat.format(ptn, a));
    	    return;
		}
    	catch (IOException e)
		{
   		    Object a[] = { server_addr.getHostName(), new Integer(port) };
   		    String ptn = res.getString("eirc.s3");
   		    status.printError(MessageFormat.format(ptn, a));
   		    return;			
		}
    	catch (SecurityException e)
		{
	    	status.printError(res.getString("eirc.not_in_applet"));
	    	return;	
		}
		catch (Exception e)
		{
	    	status.printError(res.getString("eirc.not_in_applet"));
	    	return;
		}

    	if (connected)
    	    disconnect();

    	this.quit_sent = false;

		properties.addObserver(st);
   	    this.server = st;
   	    st.start();

		this.connected = true;

		
    	/* Login into server. */
		login();
		this.current_server = host;

    	/* Enable controls that depend on being connected. */
    	control_panel.setConnected(true);

		/*
		** Enable previously opened windows.
		*/

		/* Enable opened channels and re-join */
		String c;
		String p[] = { "", "" };
		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
			ChannelWindow w = (ChannelWindow)e.nextElement();
			if (w != null)
			{
				if (p[0].length() > 0)
				{
					p[0] += ",";
					p[1] += ",";
				}
				c = w.getChannel().getTag();
				if (c != null)
				{
					p[0] += c;
					c = w.getChannel().getKey();
					if (c.length() == 0) c = " ";
					p[1] += c;
				}
					
			    w.setEnabled(true);
			}
		}
		if (p[0].length() > 0)
			sendMessage("JOIN", p);

		/* Enable privates */
	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.setEnabled(true);
  		}
		
		status.clear();
		chan_list.clear();
		who_list.clear();
    }

	public Socket createSocket(InetAddress addr, int port) throws java.io.IOException
	{
		Socket s = null;
/*
		try
		{
			port = Integer.parseInt(getParameter("ssl"));
		}
		catch (Exception e) {}
		
		if (port > 0)
		{
			try
			{
				Class SSLClass = Class.forName("javax.net.ssl.SSLSocket");
				if(SSLClass != null)
				{
					/*
					** SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
					** SSLSocket ssl = (SSLSocket)factory.createSocket(server_addr, port);
					*/
/*
					Class FactoryCLass = Class.forName("javax.net.ssl.SSLSocketFactory");
					Method getdefault = FactoryCLass.getMethod("getDefault", null);
					Object factory = getdefault.invoke(null, null);

				Method createsocket = FactoryCLass.getMethod("createSocket", new Class[] { InetAddress.class, int.class });
				Object ssl = createsocket.invoke(factory, new Object[] { addr, new Integer(port) } );
				
				s = (Socket)ssl;
			}
		}
		catch (ClassNotFoundException e)
		{
			System.out.println("Your Java system is too old and does not support SSL. You should update at www.java.com");
		}
		catch (Exception e)
		{
			System.out.println("SSL connection failed.");
		}
*/		
		s = new Socket(addr, port);
		return(s);
	}

	public void connect(String nick, String pass, String realname)
	{
		this.nick_entry.setText(nick);
		this.nicksrv_pass = pass;
		this.realname = realname;
		connect();
	}

    private synchronized void login()
	{
		if (!connected)
	    	return;

		if (irc_pass != null && irc_pass.length() > 0)
		{
	    	String p[] = { irc_pass };
	    	sendMessage("PASS", p);
		}

	    this.new_nick = nick_entry.getText();
	    String n[] = { new_nick };
	    sendMessage("NICK", n);
		
   	    String u[] = { username, "0", "0", realname };
   	    sendMessage("USER", u);
    }

    public synchronized void disconnect()
	{
		/* Disable controls that depend on being connected.
		 */
		control_panel.setConnected(false);

		if (!connected)
		    return;

		/* Logout from server.
		*/

		logout();

		/* Kill the connection.
		*/

		Socket s = server.getSocket();

		server.disconnect();
		try
		{
		    s.close();
		}
		catch (IOException e)
		{
//		    System.err.println(e);
		}

	  	this.connected = false;
		status.printError(res.getString("eirc.disconnected"));
		chat_panel.show(status_tag);

		/* Close what should be closed on disconnection.
		*/

//		closePrivates();
//		closeChannels();

		/* Disable opened windows.
		*/

		/* Disable opened channels */
		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
			ChannelWindow w = (ChannelWindow)e.nextElement();
		    w.setEnabled(false);
			w.clearUsers();
		}

		/* Disable privates */
	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.setEnabled(false);
  		}

		/*
		** Reset away
		*/

		away.setForeground(Color.blue);
		for (int i = 0; i < menu.getItemCount(); i++)
		{
			MenuItem mi = menu.getItem(i);
			if (mi instanceof CheckboxMenuItem)
				((CheckboxMenuItem)mi).setState(false);
		}
		is_away = false;
		
		/*
		** Clear modes
		*/
		setGlobalModes("-*");
    }

    private synchronized void logout()
	{
//		if (!logged_in)
//	    	return;

		if (!quit_sent)
		{
	    	String p[] = { quit_message };
	    	sendMessage("QUIT", p);
		}

		this.logged_in = false;
    }

    private synchronized void pos_login()
	{
		if (!connected)
		    return;

		this.current_nick = new_nick;
		status.requestFocus();

		if (request_motd)
		{
	    	String p[] = { };
	    	sendMessage("MOTD", p);
		}

		if (user_modes != null && user_modes.length() > 0)
		{
	    	String p[] = { this.current_nick, user_modes };
	    	sendMessage("MODE", p);
		}

		if (chan_list != null && auto_list)
			chan_list.listChannels();

    	if (default_join != null && default_join.length() > 0)
    	{
			String p[] = null;
			default_join = default_join.trim();
			int space = default_join.indexOf(" ");
			if (space > 0)
			{
				// Keyed channels
				
				p = new String[2];
				p[0] = default_join.substring(0, space);
				p[1] = default_join.substring(space + 1);
			}
			else
			{
				p = new String[1];
    	    	p[0] = default_join;
			}
			sendMessage("JOIN", p);
			default_join = null;
    	}
    }

	public void printMode(ChannelWindow cw, String prefix, String param, String msg_tag, int sound)
	{
		if (msg_tag != null)
		{
	    	Object a[] = { prefix, param };
	    	String ptn = res.getString(msg_tag);
	    	cw.printInfo(MessageFormat.format(ptn, a));
		}

		/*
		** Play an event sound only if user is focused on the channel
		*/

		if (sound > -1 && cw == getCurrentPanel())
			playSound(sound);
	}
	
	public void debug(String s)
	{
		if (debug_traffic == 1 || debug_window == null)
			System.err.println(s);
		if (debug_traffic == 2 && debug_window != null)
			debug_window.printServerNotice(s, "Debug");
	}

    public void processMessage(Message m)
	{
		String command_key = m.getCommand().toLowerCase();
		String mask = m.getPrefix();
		String params[] = m.getParameters();
        Integer t_cmd = (Integer)commands.get(command_key);

		String prefix = mask;
    	int endOff = mask.indexOf('!');
		int atHost = mask.indexOf('@');
    	if (endOff != -1)
		{
			prefix = mask.substring(0, endOff);
			if (atHost > endOff)
			{
				String user = mask.substring(endOff + 1, atHost);
				String host = mask.substring(atHost + 1);
				if (!NickInfo.hasInfos(prefix) && !isService(prefix))
				{
					NickInfo.add(prefix, "", user, host);

					// Retrieve user's info.
    				String p[] = { prefix };
    				sendMessage("WHO", p);
				}
			}
		}

    	if (debug_traffic > 0 || t_cmd == null)
		{
    	    // Strip CRLF from the end of the message.
    	    String t = m.toString();
    	    t = t.substring(0, t.length() - 2);

    	    if (debug_traffic > 0)
	    		debug("< " + DateFormat.getInstance().format(new Date()) + " " + t);


    	    if (t_cmd == null)
			{
    			if (see_everything_from_server)
				{
    		    	try
					{
						// We don't care about the result, we just want to know if it's a number.
    					Integer.parseInt(command_key);

	    				// Numeric command, take the crud out of it.
    					StringBuffer buf = new StringBuffer(params[1]);
    					for (int i = 2; i < params.length; i++)
						{
    			  		  	buf.append(' ');
    			  		  	buf.append(params[i]);
    					}

	    				status.printUnmangled(buf.toString());
    			    }
					catch (NumberFormatException e)
					{
    					// Not numeric command.
    					status.printUnmangled(t);
    	    		}
    			}
    			return;
    	    }
		}

    	// This flag is checked by NOTICE and PRIVMSG handlers.
		boolean is_prefix_ignored = false;
		String addr = NickInfo.getInetAddr(prefix);
		if (addr != null)
	    	is_prefix_ignored = ignore_list.contains(addr);

		int command = t_cmd.intValue();
    	switch (command)
    	{
        	case -1:	// PING
        	{
        	    String p[] = { params[0] };
        	    sendMessage("PONG", p);
        	    break;
        	}

        	case -2:	// NICK
        	{
        	    if (prefix.equals(current_nick))
        		{
        			// Self nick change

        			current_nick = params[0];
        			nick_entry.setText(current_nick);

        			Object a[] = { current_nick };
        			String ptn = res.getString("eirc.s4");
        			getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    }
        		else
        		{
        			// Other's nick change

        			Object a[] = { prefix, params[0] };
        			String ptn = res.getString("eirc.s5");
        			String msg_text = MessageFormat.format(ptn, a);
        			OutputWindow [] ow = getUserPanels(prefix);
        			for (int i = 0; i < ow.length; i++)
        		    	ow[i].printInfo(msg_text);
        	    }

        	    // Update nick from nick info entries
    	   		NickInfo.changeNick(prefix, params[0]);

        	    // Update private window (if there's one) accordingly
        	    if (privates.containsKey(prefix) && !privates.containsKey(params[0]))
        	    {
        			PrivateWindow pw = (PrivateWindow) privates.get(prefix);
        			pw.setUser(params[0]);
        			chat_panel.rename(prefix, params[0]);
					Object o[] = { params[0] };
					chat_panel.setTitle(params[0], private_title.format(o));
        			privates.remove(prefix);
        			privates.put(params[0], pw);
        	    }

        	    // Update channel window accordingly
        	    for (Enumeration e = channels.elements(); e.hasMoreElements(); )
        	    {
        			Channel channel = (Channel) e.nextElement();
        			if (channel.contains(prefix))
        			    channel.rename(prefix, params[0]);
        	    }
        	    break;
        	}

        	case -3:	// JOIN
        	{

        	    // If it's me, just open the channel window.
        	    if (current_nick.equals(prefix))
        	    {
        			openChannel(params[0]);

        			// Retrieve users' info.
	      			String p[] = { params[0] };
        			sendMessage("WHO", p);
        	    }
        	    else
        	    {
        			Channel channel = getChannel(params[0]);
        			if (channel != null)
        			{
	        			channel.add(prefix);
	        			if (see_join)
	        			{
	        		    	ChannelWindow cw = getChannelWindow(params[0]);
	        		    	Object a[] = { prefix };
	        		    	String ptn = res.getString("eirc.s6");
	        		    	cw.printInfo(MessageFormat.format(ptn, a));
	
							if (cw == getCurrentPanel())
								playSound(res.EVENT_JOIN);
	        			}
	        			updateChanTitle(getChannel(params[0]));
	        	    }
				}

    			break;
        	}

        	case -4:	// MODE
        	{
				String [] modes_params;
				if (params.length > 2)
				{
	        	    modes_params = new String [params.length - 2];
	        	    for (int i = 0; i < modes_params.length; i++)
	        			modes_params[i] = params[i + 2];
				}
				else
				{
					modes_params = new String[1];
					modes_params[0] = params[0];
				}

				// Process server wide user modes.

				if (!Channel.isChannel(params[0]))
				{
					setGlobalModes(params[1]);

				  	// Refresh all channels' popup menus.
	        	    for (Enumeration e = channels.elements(); e.hasMoreElements(); )
	        	    {
	        			Channel channel = (Channel) e.nextElement();
	        			channel.refresh();
	        	    }
					break;
				}

        	    Channel channel = getChannel(params[0]);
        	    channel.setModes(params[1], modes_params);

        	    ChannelWindow cw = getChannelWindow(params[0]);
        	    boolean sign = false;
        	    char modes[] = params[1].toCharArray();

        	    int j = 0;
        	    for (int i = 0; i < modes.length; i++)
        	    {
        			String msg_tag = null;
					int sound = -1;

        			switch (modes[i])
        			{
        			case '+':
        			    sign = true;
        			    break;
        			case '-':
        			    sign = false;
        			    break;
        			case 'v':
        			    msg_tag = sign ? "eirc.+v" : "eirc.-v";
						sound = sign ? res.EVENT_OP : res.EVENT_DEOP;
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
        			case 'o':
        			    msg_tag = sign ? "eirc.+o" : "eirc.-o";
						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
        			case 'h':
        			    msg_tag = sign ? "eirc.+h" : "eirc.-h";
						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
        			case 'b':
        			    msg_tag = sign ? "eirc.+b" : "eirc.-b";
						sound = sign ? res.EVENT_BAN : -1;
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
        			case 'e':
        			    msg_tag = sign ? "eirc.+e" : "eirc.-e";
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
					case 'I':
        			    msg_tag = sign ? "eirc.+I" : "eirc.-I";
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;

					// Channel modes
					case 'm':
        			    msg_tag = sign ? "eirc.+m" : "eirc.-m";
						printMode(cw, prefix, modes_params[0], msg_tag, sound);
        			    break;
					case 's':
        			    msg_tag = sign ? "eirc.+s" : "eirc.-s";
						printMode(cw, prefix, modes_params[0], msg_tag, sound);
        			    break;
					case 'i':
        			    msg_tag = sign ? "eirc.+i" : "eirc.-i";
						printMode(cw, prefix, modes_params[0], msg_tag, sound);
        			    break;
        			case 'k':
        			    msg_tag = sign ? "eirc.+k" : "eirc.-k";
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
        			case 'l':
        			    msg_tag = sign ? "eirc.+l" : "eirc.-l";
						printMode(cw, prefix, sign ? modes_params[j++] : "", msg_tag, sound);
        			    break;

    				// User modes not RFC compliant. //////////////////////////

        			case 'a':
        			    msg_tag = sign ? "eirc.+a" : "eirc.-a";
						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;
						
					case 'q':
        			    msg_tag = sign ? "eirc.+q" : "eirc.-q";
						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
        			    break;

    				// Channel modes not RFC compliant. //////////////////////////

        			case 'r':
        			    msg_tag = sign ? "eirc.+r" : "eirc.-r";
						printMode(cw, prefix, modes_params[0], msg_tag, sound);
        			    break;
        			case 'N':
        			    msg_tag = sign ? "eirc.+N" : "eirc.-N";
						printMode(cw, prefix, modes_params[0], msg_tag, sound);
        			    break;

					/////////////////////////////////////////////////////////////


					// These modes aren't handled, but are known to carry a parameter which must be skipped.
//        			case 'I':
//        			    j++;
//        			    break;
        			}
	       	    }
        	    break;
        	}

        	case -5:	// PART
        	{
        	    if (current_nick.equals(prefix))
        	    {
        			closeChannel(params[0]);
        	    }
        	    else
        	    {
        			Channel channel = getChannel(params[0]);
        			channel.remove(prefix);

        			if (see_join)
        			{
        		    	ChannelWindow cw = getChannelWindow(params[0]);
						String part_msg = "";
						if (params.length >= 2)
							part_msg = params[1];
        		    	Object a[] = { prefix, part_msg };
        		    	String ptn = res.getString("eirc.s8");
        		    	cw.printInfo(MessageFormat.format(ptn, a));

						if (cw == getCurrentPanel())
							playSound(res.EVENT_PART);
        			}
        			
        			updateChanTitle(getChannel(params[0]));
        	    }

        	    break;
        	}

        	case -6:	// QUIT
        	{
        	    Object a[] = { prefix, params[0] };
        	    String ptn = res.getString("eirc.s9");
        	    String msg_text = MessageFormat.format(ptn, a);

        	    OutputWindow [] ow = getUserPanels(prefix);
        	    for (int i = 0; i < ow.length; i++)
				{
        			ow[i].printInfo(msg_text);

					if(ow[i] == (OutputWindow)getCurrentPanel())
						playSound(res.EVENT_QUIT);
				}

        	    // Remove the user from the channels she was in.
        	    for (Enumeration e = channels.elements(); e.hasMoreElements(); )
        	    {
        			Channel channel = (Channel) e.nextElement();
        			channel.remove(prefix);
        	    }
    			updateChanTitle(getChannel(params[0]));

        	    // Remove nick info entry.
        	    NickInfo.remove(prefix);

        	    break;
        	}

        	case -7:	// KICK
        	{
        	    String reason = (params[2] != null ? params[2] : "");

        	    if (current_nick.equals(params[1]))
        	    {
          			closeChannel(params[0]);

        			Object a[] = { params[0], prefix, reason };
        			String ptn = res.getString("eirc.s10");
        			status.printInfo(MessageFormat.format(ptn, a));

					playSound(res.EVENT_KICK);
        	    }
        	    else
        	    {
        			Channel channel = getChannel(params[0]);
        			channel.remove(params[1]);

        			ChannelWindow cw = getChannelWindow(params[0]);

        			Object a[] = { params[1], prefix, reason };
        			String ptn = res.getString("eirc.s11");
        			cw.printInfo(MessageFormat.format(ptn, a));

					updateChanTitle(getChannel(params[0]));

					if(cw == getCurrentPanel())
						playSound(res.EVENT_KICK);
        	    }

        	    break;
        	}
        	case -8:	// TOPIC
        	{
        	    Channel channel = getChannel(params[0]);
        	    channel.setTopic(MircMessage.filterMircAttributes(params[1]));

        	    ChannelWindow cw = getChannelWindow(params[0]);

        	    Object a[] = { prefix, params[1] };
        	    String ptn = res.getString("eirc.s36");
        	    cw.printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case -9:	// PRIVMSG
        	{
        	    // If the source of this message is in the ignore list, ignore this message. This flag is set at the top of the switch block.
        	    if (is_prefix_ignored)
        			break;

        		boolean isChannel = Channel.isChannel(params[0]);

        	    if (CTCPMessage.isCTCPMessage(params[1]))
        		{
        			CTCPMessage ctcp = new CTCPMessage(params[1]);

        			{
        			    Object [] a = { ctcp.getCommandString(), prefix };
        			    String ptn = res.getString("eirc.ctcp_received");
//		   			    status.printWarning(MessageFormat.format(ptn, a));
        			}

            		switch (ctcp.getCommand())
            		{
            		case CTCPMessage.ACTION:
            		{
            		    if (!ctcp.hasParameter())
            				break;

       		    		ChatWindow target = (isChannel ? (ChatWindow)getChannelWindow(params[0]) : (ChatWindow)openPrivate(prefix, no_privates));
						if (target != null)
           		    		target.printAction(ctcp.getParameter(), prefix);
            		    break;
            		}
            		case CTCPMessage.PING:
            		{
            		    String param = ctcp.hasParameter() ? ctcp.getParameter() : "";
            		    CTCPMessage reply = new CTCPMessage("PING", param);
            		    String p[] = { prefix, reply.toString() };
//            		    sendMessage("NOTICE", p);
            		    break;
            		}
            		case CTCPMessage.DCC:
            		{
            		    Object a[] = { prefix };
            		    String ptn = res.getString("eirc.dcc_not_supported");
            		    OutputWindow target = getCurrentPanel();
            		    target.printInfo(MessageFormat.format(ptn, a));

              		    if (on_dcc_notify_peer)
            			{
            				String p[] = { prefix, res.getString("eirc.dcc_notify.remote")	};
            				sendMessage("NOTICE", p);

            				ptn = res.getString("eirc.dcc_notify.local");
            				target.printInfo(MessageFormat.format(ptn, a));
            		    }
            		    break;
            		}
            		case CTCPMessage.VERSION:
            		{
            		    CTCPMessage reply = new CTCPMessage("VERSION", PACKAGE.concat("-").concat(VERSION).concat(VERSION_EXTRA).concat(" ").concat(AUTHOR));

            		    String p[] = { prefix, reply.toString() };
//            		    sendMessage("NOTICE", p);
            		    break;
            		}
            		}
        	    }
        	    else
        	    {
        	   		if (isChannel)
        			{
            		    ChannelWindow target = (ChannelWindow) getChannelWindow(params[0]);
						if (target != null)
						{
        					User user = target.getChannel().get(prefix);
	        				target.printPrivmsg(params[1], prefix, user);
						}
        			}
            		else
        			{
            		    PrivateWindow target = openPrivate(prefix, no_privates);
						if (target != null)
						{
	        				target.printPrivmsg(params[1], prefix);
							playSound(res.EVENT_PRVMSG);
						}
        			}
        	    }
        	    break;
        	}

        	case -10:	// NOTICE
        	{
        	    // If the source of this message is in the ignore list, ignore this message. This flag is set at the top of the switch block.
        	    if (is_prefix_ignored)
        			break;

//        	    if (prefix.length() > 0)
//        	    {
//        			endOff = prefix.indexOf('!');
//        			if (endOff != -1)
//        			    prefix = prefix.substring(0, endOff);
//        	    }

				// NickServ checking user's password

				if (irc_services.isPassOk(prefix, params[1]))
        	    	login_time = false;

				if (irc_services.isIdPrompt(prefix, params[1]) && !login_time)
				{
        			if (current_nick.equalsIgnoreCase(original_nick) && !(nicksrv_pass != null || nicksrv_pass.equals("")))
        				identify(nicksrv_pass);
					else
	        	    	openIDWin();
				}

        	    if (irc_services.isPassBad(prefix, params[1]))
        	    	badIDWin();

        	    if (Channel.isChannel(params[0]))
        	    {

        			getChannelWindow(params[0]).printNotice(params[1], prefix);
        	    }
        	    else if (prefix.length() > 0 && prefix.toLowerCase().endsWith(network_name.toLowerCase()))
        	    {
        			// Output notices from servers to status window.

        			status.printServerNotice(params[1], res.getString("eirc.server"));
        	    }
        	    else if (special_services && prefix.length() > 0 && isService(prefix) && !Channel.isChannel(prefix))
        	    {
        			// Output notices from service bots to a dedicated (per bot) window.

        			PrivateWindow target = openPrivate(prefix, false);
					if (target != null)
	        			target.printNotice(params[1], prefix);
        	    }
        	    else if (params[0].equals(current_nick))
        	    {
        			if (!CTCPMessage.isCTCPMessage(params[1]))
        			{
        		    	// Personal notice.

        		    	OutputWindow ow = getCurrentPanel();
        		    	if (ow != null && ow != status)
        		    		ow.printNotice(params[1], prefix);
						else
							status.printNotice(params[1], prefix);
        			}
        			else
        			{
        		    	CTCPMessage ctcp = new CTCPMessage(params[1]);

        		    	switch (ctcp.getCommand())
        		    	{
        		    	case CTCPMessage.PING:
        		    	{
        					double diff = Double.NEGATIVE_INFINITY;

        					if (ctcp.hasParameter())
        					{
        			    		try
        			    		{
        							long launch = Long.parseLong(ctcp.getParameter());
        							long arrive = (new Date()).getTime();
        							diff = (arrive - launch) / 1000.0;
        			    		}
        			    		catch (NumberFormatException e) {}
        					}

       			    		Object a[] = { prefix, new Double(diff) };
       			    		MessageFormat mf = new MessageFormat(res.getString("eirc.s12"));
       			    		double limits[] = { ChoiceFormat.previousDouble(0.0), 0.0, 1.0, ChoiceFormat.nextDouble(2.0) };
       			    		String times[] = { res.getString("eirc.s12.0"), res.getString("eirc.s12.1"), res.getString("eirc.s12.2"), res.getString("eirc.s12.3")};
       			    		mf.setFormat(1, new ChoiceFormat(limits, times));
       			    		getCurrentPanel().printInfo(mf.format(a));
        					break;
        		    	}
        		    	default:
        		    	{
        					Object a[] = { ctcp.getCommandString(), ctcp.getParameter() };
        					String ptn = res.getString("eirc.ctcp_reply");
        					getCurrentPanel().printNotice(MessageFormat.format(ptn, a), prefix);
        					break;
        		    	}
        		    	}
        			}
        	    }
        	    else
        	    {
        			// These can only be originated on a server.
        			status.printNotice(params[0] + " " + params[1], prefix);
        	    }
        	    break;
        	}

        	case -11:	// ERROR
        	{
        	    status.printError(params[0]);
        	    break;
        	}

        	case -12:	// WALLOPS
        	{
        	    status.printServerNotice(params[0], prefix);
        	    break;
        	}

        	case -13:	// INVITE
        	{
				if (see_invite)
				{
        			Object a[] = { params[1], prefix };
        	    	String ptn = res.getString("eirc.invite");
        	    	status.printInfo(MessageFormat.format(ptn, a));
				}
        	    break;
        	}

			case -14:	// PONG
        	{
				if (params.length >= 1)
				{
					Object a[] = { params[1] };
	        	    String ptn = res.getString("eirc.pong");
   		     	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
				}
        	    break;
        	}

    		case 001:	// RPL_WELCOME
    		{
    		    if (m.isFromServer())
				{
					if (prefix.indexOf(".") == -1)
					{
	    		    	network_name = prefix;
					}
					else
					{
						StringTokenizer st = new StringTokenizer(prefix, ".");
						String token = "";
						while (st.countTokens() > 2)
							token = st.nextToken();
						network_name = st.nextToken() + "." + st.nextToken();
					}
					if (application)
						f.setTitle(makeWindowTitle(prefix));
				}

				if (nicksrv_pass != null && nicksrv_pass.length() > 0)
				{
					identify(nicksrv_pass);
					login_time = true;
				}

    		    break;
    		}

    		case 251:	// RPL_LUSERCLIENT
    		{
    			MessageFormat mf = new MessageFormat("There are {0} users and {1} invisible on {2} servers");
    			try
       		    {
    				Object o[] = mf.parse(params[1]);
					int visibles = 0, invisibles = 0, total = 0;
					try
					{
						visibles = Integer.parseInt((String)o[0]);
						invisibles = Integer.parseInt((String)o[1]);
						total = visibles + invisibles;
					}
					catch (NumberFormatException e) {}

					Object s[] = { new Integer(total), new Integer(invisibles) };
    				String tag = "*Who*";
    				chat_panel.setTitle(tag, wholist_title.format(s));
    			}
    	    	catch (ParseException ex) {}
    	    	break;
    		}

        	case 301:	// RPL_AWAY
			case 391:	// RPL_TIME
        	{
        		Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc." + command);
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

			case 302:	// RPL_USERHOST
        	{
        	    String list  = params[1];
				if (list.length() > 0)
				{
					StringTokenizer st = new StringTokenizer(list, " ");
					while (st.hasMoreTokens())
					{
						String s = st.nextToken();
						int i = s.indexOf("=");
						String nick = s.substring(0, i);
						if (nick.endsWith("*"))
						{
							nick = nick.substring(0, nick.length() - 1);
							Object a[] = { nick };
			        	    String ptn = res.getString("eirc.s16");
							getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
						}
						if (s.charAt(i + 1) == '-')
						{
							Object a[] = { nick };
			        	    String ptn = res.getString("eirc.302.away");
							getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
						}
						String hostmask = s.substring(i + 2);
						Object a[] = { nick, hostmask };
		        	    String ptn = res.getString("eirc.302.host");
						getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
					}
				}
        	    break;
        	}

			case 303:	// RPL_ISON
        	{
        	    String list  = params[1];
				if (list.length() == 0)
				{
					getCurrentPanel().printInfo(res.getString("eirc.isoff"));
				}
				else
				{
					StringTokenizer st = new StringTokenizer(list, " ");
					while (st.hasMoreTokens())
					{
						Object a[] = { st.nextToken() };
		        	    String ptn = res.getString("eirc.ison");
						getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
					}
				}
        	    break;
        	}

        	case 305:	// RPL_UNAWAY
        	case 306:	// RPL_NOWAWAY
        	{
        		is_away = (command == 306);
        		
        	    away.setForeground(is_away ? Color.red : Color.blue);
        	    away.repaint();	// For Macintosh MRJ
			
        	    break;
        	}

        	case 310:	// RPL_WHOISOPSTATUS
        	{
        	    Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc.310");
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 311:	// RPL_WHOISUSER
        	{
        	    if (hideip)
        	    	params[3] = "?";

       			Object a[] = { params[1], params[2], params[3] };
       			String ptn = res.getString("eirc.s13");
       			getCurrentPanel().printInfo(MessageFormat.format(ptn, a));

        	    // Real name is used to pass extra information about the user.
        	    break;
        	}

        	case 312:	// RPL_WHOISSERVER
        	{
        	    Object a[] = { params[1], params[2], params[3] };
        	    String ptn = res.getString("eirc.s15");
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 313:	// RPL_WHOISOPERATOR
        	{
        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc.s16");
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 314:	// RPL_WHOWASUSER
        	{
        	    if (hideip)
        	    	params[3] = "?";

       			Object a[] = { params[1], params[2], params[3], params[5] };
       			String ptn = res.getString("eirc.314");
       			getCurrentPanel().printInfo(MessageFormat.format(ptn, a));

        	    break;
        	}

        	case 315:	// RPL_ENDOFWHO
        	{
        	    ChannelWindow cw = getChannelWindow(params[1]);

        	    // Show "End of list" message only for channels which we haven't joined.

        	    if (cw == null && Channel.isChannel(params[1]))
        	    {
    //				Object a[] = { params[1] };
    //				String ptn = res.getString("eirc.315");
    //				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    }

       	    	updateChanTitle(getChannel(params[1]));

				if (params[1].indexOf("*") >= 0)
	   	    	 	replyWhoEnd();
				else
					who_reply = null;

    			break;
    		}

        	case 317:	// RPL_WHOISIDLE
			{
				int hrs = 0, mins = 0, secs = 0;
				try
				{
					hrs = Integer.parseInt(params[2]) / 3600;
					mins = (Integer.parseInt(params[2]) % 3600) / 60;
					secs = (Integer.parseInt(params[2]) % 3600) % 60;
				} catch (NumberFormatException e) {}
				Object a[] = { params[1], new Integer(hrs), new Integer(mins), new Integer(secs) };
        	    getCurrentPanel().printInfo(MessageFormat.format(res.getString("eirc.317"), a));

				if (params.length >= 4)
				{
					Object b[] = { params[1], new Date(Long.parseLong(params[3]) * 1000) };
					getCurrentPanel().printInfo(MessageFormat.format(res.getString("eirc.317.signon"), b));
				}
        	    break;
        	}

        	case 319:	// RPL_WHOISCHANNELS
			{
        	    Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc.s18");
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 321:	// RPL_LISTSTART
			{
        	    replyListStart();
        	    break;
        	}

        	case 322:	// RPL_LIST
			{
        	    // Some servers (ie. Bahamut) report a channel named '*' under some circumstances. Ignore those channels.
        	    if (params[1].equals("*"))
        			break;

        	    int users = 0;
        	    try
        	    {
        			users = Integer.parseInt(params[2]);
        	    }
        	    catch (NumberFormatException ex) {}

        	    replyListAdd(params[1], users, params[3]);
        	    break;
        	}
        	case 323:	// RPL_LISTEND
			{
        	    replyListEnd();
        	    break;
        	}

        	case 324:	// RPL_CHANNELMODEIS
			{
        	    String [] modes_params = new String [params.length - 3];
        	    for (int i = 0; i < modes_params.length; i++)
        			modes_params[i] = params[i + 3];

        	    Channel channel = getChannel(params[1]);
				if (channel != null)
	        	    channel.setModes(params[2], modes_params);

        	    break;
        	}

        	case 331:	// RPL_NOTOPIC
        	{
        	    Channel channel = getChannel(params[1]);
        	    if (channel != null)
        		    channel.setTopic("");

        	    OutputWindow cw = getChannelWindow(params[1]);
        	    if (cw == null)
        	    	cw = getCurrentPanel();

        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc.s37");
        	    cw.printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 332:	// RPL_TOPIC
        	{
        	    Channel channel = getChannel(params[1]);
        	    if (channel != null)
        		    channel.setTopic(MircMessage.filterMircAttributes(params[2]));

        	    OutputWindow cw = getChannelWindow(params[1]);
        	    if (cw == null)
        	    	cw = getCurrentPanel();

        	    Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc.s38");
        		cw.printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 333:	// RPL_TOPICINFO
        	{
        	    // We're sent the time in seconds, but Date expects millis.
        	    Date topic_date = new Date(Long.parseLong(params[3]) * 1000);

        	    OutputWindow cw = getChannelWindow(params[1]);
        	    if (cw == null)
        	    	cw = getCurrentPanel();

        	    Object a[] = { params[2], topic_date, topic_date };
        	    String ptn = res.getString("eirc.s35");
        	    cw.printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}
			
        	case 336:	// RPL_INVITELIST
        	{
    			listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
                break;
            }

        	case 337:	// RPL_ENDOFINVITELIST
        	{
        	    listEnd(params[1], 'I');
        	    break;
        	}

        	case 348:	// RPL_EXCEPTLIST
        	{
    			listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
                break;
            }

        	case 349:	// RPL_ENDOFEXCEPTLIST
        	{
        	    listEnd(params[1], 'e');
        	    break;
        	}

        	case 352:	// RPL_WHOREPLY
        	{
				if (params.length >= 6)
				{
    				NickInfo.add(params[5], params[params.length - 1], params[2], params[3]);
					replyWhoAdd(params[5]);
				}

          	    if (!params[1].equals("*"))
          	    {
        			ChannelWindow cw = (ChannelWindow)getChannelWindow(params[1]);
        			if (cw != null)
    	   		    	cw.refreshUsers();
    	   		}
    	   		else
    	   		{
    				for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
    				{
    				    ChannelWindow cw = (ChannelWindow)e.nextElement();
        				if (cw != null)
    		   		    	cw.refreshUsers();
    				}
    	   		}
                break;
            }

        	case 353:	// RPL_NAMREPLY
        	{
        	    Channel channel = getChannel(params[2]);

        	    StringTokenizer st = new StringTokenizer(params[3], " ");
				int tokens = st.countTokens();
        	    for (int i = 0; i < tokens; i++)
				{
					if (channel != null)
	        			channel.add(st.nextToken());
					else
						names.addElement(st.nextToken());
				}

        	    break;
        	}

			case 366:	// RPL_ENDOFNAMES
			{
				if (getChannel(params[1]) == null)
				{
					Object a[] = { params[1] };
       		    	String s = res.getString("eirc.366");
       		    	getCurrentPanel().printInfo(MessageFormat.format(s, a));
					
					s = "";
					for (Enumeration e = names.elements() ; e.hasMoreElements() ;)
						s += (String)e.nextElement() + " ";
					getCurrentPanel().printInfo(s);
				}
				names.removeAllElements();
				
				break;
			}
			
        	case 367:	// RPL_BANLIST
        	{
    			listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
                break;
            }

        	case 368:	// RPL_ENDOFBANLIST
        	{
        	    listEnd(params[1], 'b');
        	    break;
        	}

    		case 6:		// RPL_ADMINME
    		case 7:		// RPL_ADMINLOC1
    		case 8:		// RPL_ADMINLOC2
    		case 9:		// RPL_ADMINEMAIL
    		case 371:	// RPL_INFO
    		case 372:	// RPL_MOTD
    		{
    		    status.printInfo(params[1]);
    		    break;
    		}

    		case 381:	// RPL_YOUREOPER
    		{
    		    getCurrentPanel().printInfo(res.getString("eirc." + command));
    		    break;
    		}

        	case 376:   // RPL_ENDOFMOTD
        	case 422:	// ERR_NOMOTD
        	{
        		// If this point was reached, login has been successful.
        	    if (!logged_in)
        	    {
        			this.logged_in = true;

        			// See what the server thinks our nick is from the message it sent. Servers trim nicks to fit an n-char limit.
        			if (!new_nick.equals(params[0]))
        			{
        		    	this.new_nick = params[0];
        		    	nick_entry.setText(new_nick);

        		    	Object a[] = { new_nick };
        		    	String ptn = res.getString("eirc.s4");
        		    	getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        			}

        			pos_login();
        	    }
        	    break;
        	}

        	case 404:	// ERR_CANNOTSENDTOCHAN
        	{
        	    Object a[] = { params[2] };
        	    String ptn = res.getString("eirc." + command);
        	    getCurrentPanel().printError(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 432:	// ERR_ERRONEUSNICKNAME
        	case 433:	// ERR_NICKNAMEINUSE
        	{
        	    getCurrentPanel().printError(res.getString("eirc." + command));
        	    break;
        	}

        	case 401:   // ERR_NOSUCHNICK
        	case 406:	// ERR_WASNOSUCHNICK
			case 421:	// ERR_UNKNOWNCOMMAND
        	case 461:   // ERR_NEEDMOREPARAMS
        	case 464:   // ERR_PASSWDMISMATCH
        	case 491:	// ERR_NOOPERHOST
        	{
        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc." + command);
        	    getCurrentPanel().printError(MessageFormat.format(ptn, a));
        	    break;
        	}

			case 437:	// ERR_UNAVAILRESOURCE
        	case 465:   // ERR_YOUREBANNEDCREEP
        	case 471:   // ERR_CHANNELISFULL
        	case 473:   // ERR_INVITEONLYCHAN
        	case 474:   // ERR_BANNEDFROMCHAN
			case 478:   // ERR_BANLISTFULL
			case 482:	// ERR_CHANOPRIVSNEEDED
        	{
        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc." + command);
        	    status.printError(MessageFormat.format(ptn, a));
        		status.requestFocus();

        	    break;
        	}

        	case 475:	// ERR_BADCHANNELKEY
        	{
				openKeyWin(params[1]);
        	    break;
        	}

//----------------- RFC not compliant special messages -----------------

    		case 232:	// RPL_RULES (Unreal)
			case 536:	// RPL_RULES (IrcDreams)
    		{
    		    status.printInfo(params[1]);
    		    break;
    		}

        	case 307:	// RPL_WHOISREGISTERED (Unreal, Bahamut)
        	{
        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc.s27");
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 320:	// RPL_WHOISSPECIAL (Unreal)
        	{
        	    Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc." + command);
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 335:	// RPL_WHOISBOT (Unreal)
        	{
        	    Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc." + command);
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}
			
        	case 346:	// RPL_INVEXLIST (Unreal)
        	{
    			listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
                break;
            }

        	case 347:	// RPL_ENDOFINVEXLIST (Unreal)
        	{
        	    listEnd(params[1], 'I');
        	    break;
        	}

        	case 378:	// RPL_WHOISHOST (Unreal)
        	{
        	    Object a[] = { params[1], params[2] };
        	    String ptn = res.getString("eirc." + command);
        	    getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

			case 438:	// ERR_NICKCHANGETOOFAST (Unreal)
			case 447:	// ERR_NONICKCHANGE (Unreal)
        	{
				Object a[] = { params[1] };
        	    String ptn = res.getString("eirc.nickchange");
        	    getCurrentPanel().printError(MessageFormat.format(ptn, a));
        	    break;
        	}
			
        	case 498:	// Reserved nick Voila.fr
        	{
        	    getCurrentPanel().printError(res.getString("eirc.433"));
        	    break;
        	}
			
			case 477:   // ERR_NEEDREGGEDNICK (Unreal, Bahamut)
        	{
        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc." + command);
        	    status.printError(MessageFormat.format(ptn, a));
        		status.requestFocus();
        	    break;
        	}
			
        	case 600:	// RPL_LOGON (Unreal, Bahamut)
        	case 601:	// RPL_LOGOFF (Unreal, Bahamut)
        	{
        		Date log = new Date(Long.parseLong(params[4]) * 1000);

        	    Object a[] = { params[1], log };
        	    String ptn = res.getString("eirc." + command);
        	    status.printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}

        	case 604:	// RPL_NOWON (Unreal, Bahamut)
        	case 605:	// RPL_NOWOFF (Unreal, Bahamut)
        	{
        	    Object a[] = { params[1] };
        	    String ptn = res.getString("eirc." + command);
        	    status.printInfo(MessageFormat.format(ptn, a));
        	    break;
        	}
//-----------------------------------------------------------------------

    	}
    }

    private void replyListStart()
    {
    	chan_list.clear();
    }

    private void replyListAdd(String tag, int users, String topic)
    {
		// Some buggy IRCd's don't send RPL_LISTSTART.
//		if (!chan_list.initialized())
//	    	replyListStart();

		chan_list.add(new ChannelItem(tag, users, topic));
		
		if (chan_list.number() % 100 == 0);
			updateChanListTitle();
    }

    private void replyListEnd()
    {
		// Some buggy IRCd's don't send RPL_LISTSTART.
//		if (!chan_list.initialized())
//	    	replyListStart();
	    	
		if (chan_list.number() == 0)
			status.printInfo(res.getString("eirc.s19"));

		chan_list.stop();
		updateChanListTitle();
    }

	private void replyWhoAdd(String tag)
    {
		if (who_reply == null)
	    	who_reply = new Vector();

		who_reply.addElement(tag);
    }

    private void replyWhoEnd()
    {
		if (who_reply == null)
	    	who_reply = new Vector();

	    who_list.loadUsers(who_reply);
		this.who_reply = null;
    }

    private void listStart()
    {
    	this.list = new Vector(1);
    }

    private void listAdd(String mask, String op, Date date)
    {
		if (list == null)
	    	listStart();

		list.addElement(new ListItem(mask, op, date));
    }

    private void listEnd(String channel, char param)
    {
		if (list == null)
	    	listStart();
		String supported_modes = "beI";
		if (supported_modes.indexOf(param) >= 0)
		    openList(channel, param);
		this.list = null;
    }

    public void sendCommand(String text, OutputWindow target)
	{
		if (text.trim().length() <= 0)
	  	    throw new IllegalArgumentException("empty command");

		String [] result = null;
		try
		{
	    	result = Commands.parseCommand(user_commands, text);
		}
		catch (MissingResourceException ex)
		{
	    	// Let the server decide whether the command is correct.
	    	String [] a = new String [0];
	    	sendMessage(text, a);

		    return;
		}
		catch (IllegalArgumentException ex)
		{
	    	// Peek command. Text won't be neither empty nor have only white space (checked above).
	    	String name = new StringTokenizer(text).nextToken();

	    	// If excution gets here, command "name" does exist, otherwise the previous catch statement would have came into action.
	    	Command c = Commands.getCommand(user_commands, name);
	    	String action = c.getTag();
	    	int required = c.getRequiredParameters();

		    // Show help.

			String help = res.getString(action.concat(".short").toLowerCase());
			if (help != null)
			{
				Object [] a = { help };
				String ptn = res.getString("eirc.s22");
				target.printError(MessageFormat.format(ptn, a));
			}
			else
			{
				// No help for this command. Warn the user.
				MessageFormat mf = new MessageFormat(res.getString("eirc.bad_invocation"));
				Object [] a = { action, new Integer(required) };
				double [] limits = { 1.0, ChoiceFormat.nextDouble(1.0) };
				String [] fragments =
				{
				    res.getString("eirc.bad_invocation.0"),
				    res.getString("eirc.bad_invocation.1"),
				};
				mf.setFormat(1, new ChoiceFormat(limits, fragments));
				target.printError(mf.format(a));
		    }

		    return;
		}

		/* Invoke the command. */

		String name = result[0];
		Vector parameters = new Vector(result.length - 1);
		for (int i = 1; i < result.length; i++)
		    parameters.addElement(result[i]);

		invokeCommand(name, parameters, target);
    }

    void invokeCommand(String name, Vector parameters, OutputWindow target)
	{
		// Command disabled in config ?
		if (isDisabledCmd(name))
		{
			Object [] a = { name };
			String ptn = res.getString("eirc.forbid");
			target.printError(MessageFormat.format(ptn, a));
			return;
		}
		
		// Only add commands here, DON'T add aliases.
		if (name.equalsIgnoreCase("HELP"))
	    	cmd_help(parameters, target);
		else if (name.equalsIgnoreCase("CLEAR"))
	    	cmd_clear(parameters, target);
		else if (name.equalsIgnoreCase("QUOTE"))
	    	cmd_quote(parameters, target);
		else if (name.equalsIgnoreCase("JOIN"))
	    	cmd_join(parameters, target);
		else if (name.equalsIgnoreCase("PART"))
	    	cmd_part(parameters, target);
		else if (name.equalsIgnoreCase("PRIVMSG"))
	    	cmd_msg(parameters, target);
		else if (name.equalsIgnoreCase("NOTICE"))
		    cmd_notice(parameters, target);
		else if (name.equalsIgnoreCase("PINGTIME"))
	    	cmd_pingtime(parameters, target);
		else if (name.equalsIgnoreCase("QUIT"))
	    	cmd_quit(parameters, target);
		else if (name.equalsIgnoreCase("NICK"))
		    cmd_nick(parameters, target);
		else if (name.equalsIgnoreCase("ME"))
		    cmd_me(parameters, target);
		else if (name.equalsIgnoreCase("QUERY"))
		    cmd_query(parameters, target);
		else if (name.equalsIgnoreCase("CTCP"))
		    cmd_ctcp(parameters, target);
		else if (name.equalsIgnoreCase("EIRC"))
		    cmd_eirc(parameters, target);
		else if (name.equalsIgnoreCase("KBAN"))
		    cmd_kban(parameters, target);
		else if (name.equalsIgnoreCase("IGNORE"))
		    cmd_ignore(parameters, target);
		else if (name.equalsIgnoreCase("UNIGNORE"))
		    cmd_unignore(parameters, target);
		else if (name.equalsIgnoreCase("SERVER"))
		    cmd_server(parameters, target);
		else if (name.equalsIgnoreCase("GHOST"))
		    cmd_ghost(parameters, target);
		else if (name.equalsIgnoreCase("DEBUG"))
		    cmd_debug(parameters, target);
		else
		{
	    	// Let the server decide whether the command is correct.
	    	String [] a = new String [parameters.size()];
	    	parameters.copyInto(a);
	    	sendMessage(name, a);
		}
    }
	
	boolean isDisabledCmd(String cmd)
	{
		String list = getParameter("disabled_cmds");
		if (list != null && cmd != null && list.length() > 0 && cmd.length() > 0)
		{
			StringTokenizer tk = new StringTokenizer(list, ", ");
    		while (tk.hasMoreTokens())
    		{
    			if (tk.nextToken().equalsIgnoreCase(cmd))
					return(true);
    		}
		}
		
		return(false);		
	}

    void cmd_help(Vector params, OutputWindow target)
	{
		// If no command has been specified.
  		if (params.size() == 0)
		{
  	    	// List internal commands.
	    	for (Enumeration e= user_commands.getKeys();e.hasMoreElements();)
				target.printInfo((String) e.nextElement());
	    	return;
  		}

		String action = (String)params.elementAt(0);
		try
		{
	    	// Resolve aliases.
	    	action = Commands.getCommand(user_commands, action).getTag();
		}
		catch (MissingResourceException e)
		{
		}

   		String help_on = action.toLowerCase();
	    // See if there's help locally provided for this command.
		String h1 = res.getString(help_on.concat(".short"));
		String h2 = res.getString(help_on.concat(".long"));
		if (h1 != null && h2 != null)
		{
	    	target.printInfo(h1);
	    	target.printInfo(h2);
		}
		else
		{
    		// No help for this command.
    		Object a[] = { action };
    		String ptn = res.getString("eirc.no_help");
    		target.printError(MessageFormat.format(ptn, a));
		}
    }

    void cmd_quote(Vector params, OutputWindow target)
	{
		String a[] = new String [0];
		sendMessage((String) params.elementAt(0), a);
    }

    void cmd_nick(Vector params, OutputWindow target)
	{
		String a[] = new String [params.size()];
		params.copyInto(a);
		a[0] = RFC1459.filterString(a[0]);

		sendMessage("NICK", a);
    }

    void cmd_me(Vector params, OutputWindow target)
	{
		if (!(target instanceof ChatWindow))
		{
	    	target.printError(res.getString("eirc.s23"));
	    	return;
		}

		String to = ((ChatWindow) target).getPanelTag();
		params.insertElementAt(to, 0);

		String a[] = new String [params.size()];
		params.copyInto(a);

		((ChatWindow) target).printMyAction(a[1]);

		a[1] = "\001ACTION " + a[1] + "\001";
		sendMessage("PRIVMSG", a);
    }

    void cmd_query(Vector params, OutputWindow target)
	{
		String a[] = new String [params.size()];
		params.copyInto(a);

		PrivateWindow pw = openPrivate(a[0]);
		chat_panel.show(a[0]);
		pw.requestFocus();

		if (params.size() == 2)
		{
	    	sendMessage("PRIVMSG", a);
	    	pw.printMyPrivmsg(a[1]);
		}
    }
    void cmd_msg(Vector params, OutputWindow target)
	{
		cmd_query(params, target);
    }
	
    void cmd_notice(Vector params, OutputWindow target)
	{
		if (!(target instanceof ChatWindow))
		{
	    	target.printError(res.getString("eirc.s23"));
	    	return;
		}

		if (params.size() == 2)
		{
			String a[] = new String [2];
			a[0] = (String)params.elementAt(0);
			a[1] = (String)params.elementAt(1);
			sendMessage("NOTICE", a);
		   	((ChatWindow)target).printMyNotice(a[1]);
		}
    }
	
    void cmd_join(Vector params, OutputWindow target)
	{
		if (!canJoin())
			return;
				
		String a[] = new String [params.size()];
		params.copyInto(a);

		if (!Channel.isChannel(a[0]))
		{
	    	a[0] = '#' + a[0];
		}
		sendMessage("JOIN", a);
    }

    void cmd_part(Vector params, OutputWindow target)
	{
		if (0 == params.size())
		{
	    	if (!(target instanceof ChannelWindow))
			{
				target.printError(res.getString("eirc.s24"));
				return;
	    	}
	    	params.insertElementAt(((ChannelWindow) target).getPanelTag(), 0);
		}

		String a[] = new String [params.size()];
		params.copyInto(a);

		if (!Channel.isChannel(a[0]))
		{
	    	a[0] = '#' + a[0];
		}

		// The Listener for the event takes care of sending the message.
	  	sendMessage("PART", a);
    }

    void cmd_pingtime(Vector params, OutputWindow target)
	{
		params.addElement("\001PING " + (new Date()).getTime() + "\001");

		String a[] = new String [params.size()];
		params.copyInto(a);

		sendMessage("PRIVMSG", a);
    }

    void cmd_quit(Vector params, OutputWindow target)
	{
		String a[] = new String [1];

		a[0] = 0 == params.size() ? quit_message : (String) params.elementAt(0);

		this.quit_sent = true;
		sendMessage("QUIT", a);
    }

    void cmd_ctcp(Vector params, OutputWindow target)
	{
		String a[] = new String [params.size()];
		params.copyInto(a);

		a[1] = "\001" + a[1] + "\001";
		sendMessage("PRIVMSG", a);
    }

    void cmd_eirc(Vector params, OutputWindow target)
	{
		Object a[] = { PACKAGE, VERSION.concat(" ").concat(VERSION_EXTRA), res.getString("author"), res.getString("update") };
		String ptn = res.getString("info");
		target.printInfo(MessageFormat.format(ptn, a));
    }

	void cmd_clear(Vector params, OutputWindow target)
	{
		target.clear();
    }

    void cmd_kban(Vector params, OutputWindow target)
	{
		int i = 0;
		String channel = "";
		if (target instanceof ChannelWindow)
			channel = ((ChannelWindow)target).getPanelTag();
		if (params.size() > 2)
			channel = (String)params.elementAt(i++);
		String nick = (String)params.elementAt(i++);
		String why = "";
		if (i == params.size() - 1)
			why = (String)params.elementAt(i);

    	if (getChannel(channel) == null)
		{
			target.printError(res.getString("eirc.s24"));
			return;
    	}

		String mask = nick;
		String addr = NickInfo.getInetAddr(nick);
		String user = NickInfo.getUser(nick);
		if (addr == null)
		{
			if (user != null)
				mask = "*!" + user + "@*";
		}
		else
		{
			mask = "*!*@" + addr;
		}

	  	sendCommand("MODE " + channel + " +b " + mask, target);
  		sendCommand("KICK " + channel + " " + nick + " " + why, target);
    }

    void cmd_ignore(Vector params, OutputWindow target)
	{
		if (params.size() == 0)
		{
	    	if (ignore_list.size() == 0)
			{
				target.printWarning(res.getString("eirc.no_ignored_users"));
				return;
		    }

		    StringBuffer line = new StringBuffer();
		    for (Enumeration e = NickInfo.getList().elements(); e.hasMoreElements();)
			{
				String nick = (String) e.nextElement();
				if (ignore_list.contains(NickInfo.getInetAddr(nick)))
					line.append(' ').append(nick);
			}

	    	String [] a = { line.toString() };
		    String ptn = res.getString("eirc.ignored_users");
		    target.printInfo(MessageFormat.format(ptn, a));
		    return;
		}

		String nick = (String) params.elementAt(0);
		String addr = NickInfo.getInetAddr(nick);

		if (addr != null && !ignore_list.contains(addr))
		{
		    ignore_list.addElement(addr);

		    Object a[] = { nick };
		    String ptn = res.getString("eirc.ignore");
	    	target.printInfo(MessageFormat.format(ptn, a));

	    	String p[] = { nick, res.getString("eirc.ignore_notice") };
	    	sendMessage("NOTICE", p);
		}
    }

    void cmd_unignore(Vector params, OutputWindow target)
	{
		String nick = (String) params.elementAt(0);
		String addr = NickInfo.getInetAddr(nick);

		if (addr != null && ignore_list.removeElement(addr))
		{
	    	Object a[] = { nick };
	    	String ptn = res.getString("eirc.unignore");
	    	target.printInfo(MessageFormat.format(ptn, a));

	    	String p[] = { nick, res.getString("eirc.unignore_notice") };
	    	sendMessage("NOTICE", p);
		}
    }

    void cmd_server(Vector params, OutputWindow target)
	{
		String server_name = (String)params.elementAt(0);
		if (params.size() > 2)
			this.irc_pass = (String)params.elementAt(2);

		// If user passed a port number.
		if (params.size() > 1)
		{
	    	try
			{
				connect(server_name, Integer.parseInt((String)params.elementAt(1)));
	    	}
			catch (NumberFormatException e)
			{
				// Connect to default port.
				connect(server_name);
	    	}
		}
		else
		{
		    // Connect to default port.
		    connect(server_name);
		}
    }

    void cmd_ghost(Vector params, OutputWindow target)
	{
		String a[] = new String [params.size()];
		params.copyInto(a);
		irc_services.killGhost(a[0], a[1]);
    }
	
	void cmd_debug(Vector params, OutputWindow target)
	{
		String a[] = new String [params.size()];
		params.copyInto(a);
		if (a[0].equalsIgnoreCase("OFF"))
			debug_traffic = 0;
		if (a[0].equalsIgnoreCase("CONSOLE"))
			debug_traffic = 1;
		if (a[0].equalsIgnoreCase("ON"))
		{
			debug_window = getCurrentPanel();
			debug_traffic = 2;
		}
    }

    public void sendMessage(String command, String parameters[])
	{
		if (connected)
		{
	    	Message m = new MircMessage(command, parameters);
	    	if (debug_traffic > 0)
			{
				String t = m.toString();
				// Strip CRLF.
				t = t.substring(0, t.length() - 2);
	    		debug("> " + DateFormat.getInstance().format(new Date()) + " " + t);
	    	}

		    try
			{
				server.enqueueMessage(m);
		    }
			catch (IOException e)
			{
		    	System.err.println(e);
		    }
		}
		else
		{
	    	// FIXME: it can't distinguish user and program issued messages.
	  	    status.printError(res.getString("eirc.disconnected"));
		}
    }

	public void joinChannel(String name)
    {
		if (getChannel(name) == null)
		{
			if (canJoin())
			{
	    		String p[] = { name };
		    	sendMessage("JOIN", p);
			}
		}
		else
		{
	    	showPanel(name);
		}
    }
	
    public boolean canJoin()
	{
		// Permission ?
		if (restrict_join == 0)
			return(true);
			
		return(false);
    }
	
    public OutputWindow getCurrentPanel()
	{
		ChatPanel cp = chat_panel.getVisible();
		return (cp != null && cp instanceof OutputWindow ? (OutputWindow)cp : status);
    }

    public OutputWindow getUserPanel(String user)
	{
		OutputWindow ow = getPrivate(user);

		return (ow != null ? ow : status);
    }

    public OutputWindow [] getUserPanels(String user)
	{
		Vector v = new Vector();

		for (Enumeration e = channel_windows.elements(); e.hasMoreElements();)
		{
	    	ChannelWindow cw = (ChannelWindow) e.nextElement();
	    	Channel c = cw.getChannel();

		    if (c.contains(user))
				v.addElement(cw);
		}

		OutputWindow pw = getPrivate(user);
		if (pw != null)
	    	v.addElement(pw);

		OutputWindow [] a = new OutputWindow [v.size()];
		v.copyInto(a);

		return(a);
    }

    public String[] getChans()
	{
		Vector v = new Vector();

		for (Enumeration e = channel_windows.elements(); e.hasMoreElements();)
		{
	    	ChannelWindow cw = (ChannelWindow) e.nextElement();
	    	Channel ch = cw.getChannel();
			v.addElement(ch.getTag());
		}

		String[] a = new String[v.size()];
		v.copyInto(a);

		return(a);
    }

    public String getNick()
	{
		return(current_nick);
    }

    public Channel getChannel(String tag)
	{
		ChannelWindow cw = getChannelWindow(tag);
		return (cw != null ? cw.getChannel() : null);
    }

    public ChannelWindow getChannelWindow(String tag)
	{
		return (ChannelWindow)channel_windows.get(tag);
    }

    public Channel openChannel(String tag)
	{
		Channel channel = getChannel(tag);
		if (channel != null)
		{
			chat_panel.show(tag);
	    	return(channel);
		}

		channel = new Channel(tag);
	  	channels.addElement(channel);

		String [] p = { tag };
		sendMessage("MODE", p);

		/* Create GUI object.
		 */

		ChannelWindow cw = new ChannelWindow(this, channel);
		properties.addObserver(cw);
		cw.update(properties, null);

		cw.setFont(getFont());
		cw.setForeground(mainfg);
		cw.setBackground(mainbg);
		cw.setTextForeground(textfg);
		cw.setTextBackground(textbg);
		cw.setSelectedForeground(selfg);
		cw.setSelectedBackground(selbg);

		channel_windows.put(tag, cw);

		chat_panel.add(cw, tag);

	  	cw.validate();

		chat_panel.show(tag);

  		cw.requestFocus();
		cw.addChatPanelListener(this);

		return channel;
    }

	public void closeChannels()
	{
		/* Close all open channels panels */
		
		String chans[] = getChans();
		String param[] = new String[1];

		if (connected && chans.length > 0)
		{
			int i = chans.length - 1;
			param[0] = chans[i--];
			for (; i >= 0; i--)
				param[0] += "," + chans[i];

			sendMessage("PART", param);
		}

		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
			ChannelWindow w = (ChannelWindow)e.nextElement();
		    w.dispose();
		}
		channel_windows.clear();
	  	channels.removeAllElements();
	}
	
	public void closeChannel(String s)
	{
		channels.removeElement(s);
		ChannelWindow cw = getChannelWindow(s);
		if (cw != null)
			cw.dispose();
	}

	public void closePrivates()
	{
		/* Close privates panels */
		
		for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.dispose();
  		}
		privates.clear();
	}

    public void updateChanTitle(Channel ch)
    {
    	if (ch != null)
    	{
	    	String tag = ch.getTag();
	    	Object o[] = { tag, new Integer(ch.number()) };
			chat_panel.setTitle(tag, chan_title.format(o));
		}
    }

	public void updateChanListTitle()
	{
		String tag = "*Chans*";
		Object o[] = { new Integer(chan_list.number()) };
		chat_panel.setTitle(tag, chanlist_title.format(o));
	}

    public PrivateWindow getPrivate(String target)
	{
		return (PrivateWindow) privates.get(target);
    }

    public PrivateWindow openPrivate(String target, boolean no_more)
	{
		PrivateWindow pw = getPrivate(target);
		if (pw != null)
		    return pw;

		if (no_more)
			return(null);
		else
			return(openPrivate(target));
	}

    public PrivateWindow openPrivate(String target)
	{
		PrivateWindow pw = getPrivate(target);
		if (pw != null)
		    return pw;

		pw = new PrivateWindow(this, target, target);
		properties.addObserver(pw);
		pw.update(properties, null);

		pw.setFont(getFont());
		pw.setForeground(mainfg);
		pw.setBackground(mainbg);
		pw.setTextForeground(textfg);
		pw.setTextBackground(textbg);
		pw.setSelectedBackground(selbg);

		privates.put(target, pw);
		chat_panel.add(pw, target);

		Object o[] = { target };
		chat_panel.setTitle(target, private_title.format(o));

		pw.validate();
		if (focus_opening_privates)
		{
	    	chat_panel.show(target);
	    	pw.requestFocus();
		}

		pw.addChatPanelListener(this);

		return pw;
    }

    public void showPanel(String name)
	{
		chat_panel.show(name);
    }

    public void openChannelList()
	{
		chat_panel.showPanel("*Chans*");
    }

    public void openWhoList()
	{
//		String p[] = { };
//	    sendMessage("LUSERS", p);
		chat_panel.showPanel("*Who*");
	}

    public void openConfigurator()
	{
		if (configurator == null)
		{
			configurator = new Configurator(this, getFrame(), this.properties);
			configurator.addWindowListener(this);
			configurator.setName("configurator");
		}
		else
		{
			configurator.toFront();
		}
    }

    public void openList(String tag, char mode)
	{
    	if (list != null)
    	{
    		Channel chan = getChannel(tag);
    		boolean is_op = false;
    		if (chan != null)
    		{
    			User me = chan.get(getNick());
    			is_op = me.isOwner() || me.isAdmin() || me.isOp() || me.isHalfOp() || canOverride();
    		}

			MessageFormat mf = new MessageFormat(res.getString(mode + "_list.title"));
			Object o[] = { tag };
    		String title = mf.format(o);
			mf = new MessageFormat(Resources.getString("eirc." + mode + "_list"));
			String button = res.getString(mode + "_list.delete");

			if (mode_list == null)
			{
				mode_list = new ModeList(this, getFrame());
				mode_list.addWindowListener(this);
				mode_list.setName("mode_list");
			}
			else
			{
				mode_list.toFront();
			}
			mode_list.initList(tag, is_op, list, title, mf, button, mode);
		}
    }

    public String openAwayWin()
	{
		Box b = new Box(this, getFrame(), res.getString("away.title"), res.getString("away.prompt"), res.getString("away.label"), away_str, false);
        if (b.getResult() == 1)
		{
			away_str = b.getString();
        	return(away_str != null ? away_str : "");
		}
		return("");
    }

	public Box openWin(String cmd, String nick)
	{
		String key = cmd.toLowerCase();
		MessageFormat mf = new MessageFormat(res.getString(key + ".prompt"));
		Object o[] = { nick };
    	String prompt = mf.format(o);
		return(new Box(this, getFrame(), res.getString(key + ".title"), prompt, res.getString(key + ".label"), null, false));
	}

	public void openKill(String cmd, String nick, OutputWindow target)
	{
		Box b = openWin(cmd, nick);
		String why = b.getString();
        if (b.getResult() == 1 && why.length() > 0)
        	sendCommand(cmd + " " + nick + " " + why, target);
    }

	public void openKick(String cmd, String channel, String nick, OutputWindow target)
	{
		Box b = openWin(cmd, nick);
		String why = b.getString();
        if (b.getResult() == 1 && why.length() > 0)
        	sendCommand(cmd + " " + channel + " " + nick + " " + why, target);
    }

    public void openKeyWin(String chan)
	{
		MessageFormat mf = new MessageFormat(res.getString("join.title"));
		Object o[] = { chan };
    	String title = mf.format(o);
	    Box b = new Box(this, getFrame(), title, res.getString("join.prompt"), res.getString("join.label"), null, true);
	    if (b.getResult() == 1)
	    {
			String p[] = { chan, b.getString() };
		    sendMessage("JOIN", p);
	    }
    }

    public void openGhostWin(String cmd, String nick, OutputWindow target)
    {
	    Box b = new Box(this, getFrame(), res.getString("ghost.title"), res.getString("ghost.prompt"), res.getString("ghost.label"), null, true);
		String pw = b.getString();
	    if (b.getResult() == 1 && pw.length() > 0)
			sendCommand(cmd + " " + nick + " " + pw, target);
    }

    public void openIDWin()
	{
	    Box b = new Box(this, getFrame(), res.getString("services.id.title"), res.getString("services.prompt"), res.getString("services.label"), null, true);
		String pw = b.getString();
	    if (b.getResult() == 1 && pw.length() > 0)
	    {
			identify(pw);
	    }
	    else
	    {
			String p[] = { original_nick };
		    sendMessage("NICK", p);
	    }
    }

    public void badIDWin()
    {
    	new Box(this, getFrame(), res.getString("services.id.title"), res.getString("services.ns.loginbad"), null);
		String p[] = { original_nick };
		sendMessage("NICK", p);
    }
	
	public boolean isNickCorrect(String nick)
	{
		int i = 0;
		boolean compliant = true;
		char c = '?';
		while (i < nick.length() && compliant)
		{
			c = nick.charAt(i);
			compliant = RFC1459.isDeclaredChar(c);
			i++;
		}
		if (!compliant)
		{
			Object o[] = { new Character(c) };
			MessageFormat mf = new MessageFormat(res.getString("nick.message"));
			new Box(this, getFrame(), res.getString("nick.title"), mf.format(o), null, null, false);
			return(false);
		}
		return(true);
	}
	
    public void openHelp()
	{
		if (help == null)
		{
			help = new HelpBox(this, getFrame());
			help.addWindowListener(this);
			help.setName("help");
		}
		else
		{
			help.toFront();
		}
    }

    public String makeWindowTitle(String t)
	{
		if (t == null)
			t = "";

		String te;
		if ((te = res.getString("title")) != null)
		{
		    // Force a space char at the beginning, browsers trim PARAMs.
	    	if (te.charAt(0) != ' ')
				te = ' ' + te;
	    	t = t.concat(te);
		}

		return t;
    }

    public void update(Observable o, Object argument)
	{
		if (o instanceof ConfigurationProperties)
		{
			ConfigurationProperties props = (ConfigurationProperties)o;
			
			String arg = null;
			if (argument != null)
				arg = (String)argument;

	    	if (arg == null || arg.equals("special_services"))
	    	    this.special_services = props.getBoolean("special_services");

	    	if (arg == null || arg.equals("nicksrv_pass"))
	    	    this.nicksrv_pass = props.getString("nicksrv_pass");

	    	if (arg == null || arg.equals("irc_pass"))
	    	    this.irc_pass = props.getString("irc_pass");

	    	if (arg == null || arg.equals("quit_message"))
	    	    this.quit_message = props.getString("quit_message");

	    	if (arg == null || arg.equals("request_motd"))
	    	    this.request_motd = props.getBoolean("request_motd");

	    	if (arg == null || arg.equals("see_everything_from_server"))
	    	    this.see_everything_from_server	= props.getBoolean("see_everything_from_server");

	    	if (arg == null || arg.equals("see_join"))
    		    this.see_join = props.getBoolean("see_join");

	    	if (arg == null || arg.equals("see_invite"))
	    	    this.see_invite = props.getBoolean("see_invite");

	    	if (arg == null || arg.equals("on_dcc_notify_peer"))
	    	    this.on_dcc_notify_peer = props.getBoolean("on_dcc_notify_peer");

	    	if (arg == null || arg.equals("service_bots"))
	    	    this.service_bots = props.getString("service_bots");

	    	if (arg == null || arg.equals("hideip"))
    		    this.hideip = props.getBoolean("hideip");

 			if (arg == null || arg.equals("focus_opening_privates"))
    	    	this.focus_opening_privates = props.getBoolean("focus_opening_privates");

			if (arg == null || arg.equals("no_privates"))
   		 	    this.no_privates = props.getBoolean("no_privates");

			if (arg == null || arg.equals("write_color"))
    		    this.write_color = props.getInt("write_color");

			if (arg == null || arg.equals("scroll_speed"))
   			    this.scroll_speed = props.getInt("scroll_speed");

			if (arg == null || arg.equals("silent"))
    		    this.silent = props.getInt("silent");

			if (arg == null || arg.equals("auto_list"))
    		    this.auto_list = props.getBoolean("auto_list");
				
			if (arg == null || arg.equals("net_encoding"))
    		    this.net_encoding = props.getString("net_encoding");
				
			if (arg != null && arg.startsWith("event_"))
			{
				try
				{
					int i = Integer.parseInt(arg.substring(6));
					if (i > 0 && i <= res.EVENTS && properties.getString(arg) != null)
					    this.event_sounds[i - 1] = (AudioClip)res.SOUNDS.get(properties.getString(arg));
				}
				catch(NumberFormatException ex) {}
			}
			
			if (arg == null || arg.equals("restrict_join"))
    		    this.restrict_join = props.getInt("restrict_join");
		}

		if (argument instanceof String)
		{
			String sound_name = (String)argument;
			for (int i = 1; i <= res.EVENTS; i++)
			{
				if (properties.getString("event_" + i).equals(sound_name))
					this.event_sounds[i - 1] = (AudioClip)res.SOUNDS.get(sound_name);
			}
		}
    }

    public void requestFocus()
	{
		if (connected && status != null)
	  	    status.requestFocus();
		if (!connected && nick_entry != null)
		    nick_entry.requestFocus();
    }

	public boolean isService(String tag)
	{
		return (status_tag.equals(tag) || irc_services.isService(tag));
	}

	public void setGlobalModes(String modes)
	{
		char [] mode_ary = modes.toCharArray();
		boolean sign = false;

    	int j = 0;
    	for (int i = 0; i < mode_ary.length; i++)
    	{
    	    switch (mode_ary[i])
    	    {
    	    case '+':
    			sign = true;
    			break;
    	    case '-':
	    		sign = false;
    			break;
			case 'o':
				server_admin = sign;
    			break;
			case 'i':
				who_invisible = sign;
				who_list.invisible.setState(sign);
    			break;
			case 'N':
				ircop_override = sign;
    			break;
			case '*':
				server_admin = sign;
				who_invisible = sign;
				who_list.invisible.setState(sign);
				ircop_override = sign;
			}
		}
	}

	public boolean isIRCop()
	{
		return(server_admin);
	}

	public boolean canOverride()
	{
		// If true, ircops can override a channel's modes without beeing opped. (Unreal global mode)

		return(ircop_override);
	}

	public boolean isInvisible()
	{
		return(who_invisible);
	}

    public void identify(String passwd)
    {
    	irc_services.identifyNick(passwd);
    }

    public void register(String passwd, String email)
    {
    	irc_services.registerNick(passwd, email);
    }

	public int scrollSpeed()
	{
		return(scroll_speed);
	}

	public String[] getUserColors()
	{
		String c[] = new String[color_list.size()];
		int i = 0;
		for (Enumeration e = color_list.keys(); e.hasMoreElements(); )
			c[i++] = (String)e.nextElement();

		return(c);
	}

	public Color getUserColor(String s)
	{
		return((Color)color_list.get(s));
	}

	public void paint(Graphics g)
	{
		getFrame().setBackground(getBackground());
		super.paint(g);
	}

	public Frame getFrame()
	{
		if (f != null)
			return(f);
   		Container c = this;
	    while(c != null && !(c instanceof Frame))
			c = c.getParent();
		if (c == null)
			return(new Frame());
		else
			return((Frame)c);
	}

    public void setFont(Font ft)
	{
		Frame ff = getFrame();
		mainfont = ft;
		ff.setFont(ft);
		ff.validate();

	  	status.setFont(ft);
	  	status.validate();
	  	chan_list.setFont(ft);
	  	chan_list.validate();
	  	who_list.setFont(ft);
	  	who_list.validate();

	  	// Propagate changes to other windows.
	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
	  	    ChannelWindow cw = (ChannelWindow) e.nextElement();
  		    cw.setFont(ft);
  		    cw.validate();
  		}
	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.setFont(ft);
  	    	pw.validate();
  		}

		// Popup menu font
		for (int i = 0; i < menu.getItemCount(); i++)
			menu.getItem(i).setFont(ft);

		label.setFont(ft);
  		nick_entry.setFont(ft);
		away.setFont(ft);
		control_panel.setFont(ft);
		control_panel.repaint();
		chat_panel.setFont(ft);
		chat_panel.validate();
    }

    public Font getFont()
    {
		return(mainfont);
    }

    public void setBackground(Color c)
	{
		mainbg = c;
		getFrame().setBackground(c);
		super.setBackground(c);

	  	status.setBackground(c);
	 	chan_list.setBackground(c);
	  	who_list.setBackground(c);

	  	// Propagate changes to other windows.
	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
	  	    ChannelWindow cw = (ChannelWindow) e.nextElement();
  		    cw.setBackground(c);
  		}
	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.setBackground(c);
  		}

		label.setBackground(c);
		away.setBackground(c);
		control_panel.setBackground(c);
		chat_panel.setBackground(c);
		
		repaint();
    }

    public void setForeground(Color c)
	{
		mainfg = c;
		getFrame().setForeground(c);
		control_panel.setForeground(c);
		chat_panel.setForeground(c);
    }

    public void setTextBackground(Color c)
	{
		textbg = c;
		nick_entry.setBackground(c);
	  	status.setTextBackground(c);
	  	chan_list.setTextBackground(c);
	  	who_list.setTextBackground(c);

	  	// Propagate changes to other windows.
	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
	  	    ChannelWindow cw = (ChannelWindow) e.nextElement();
  		    cw.setTextBackground(c);
  		}
	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.setTextBackground(c);
  		}
    }

    public void setTextForeground(Color c)
	{
		textfg = c;
		nick_entry.setForeground(c);
    }
	
    public void setSelectedBackground(Color c)
	{
	  	// Propagate changes to other windows.
	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
		{
	  	    ChannelWindow cw = (ChannelWindow) e.nextElement();
  		    cw.setSelectedBackground(c);
  		}
	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
		{
  	    	PrivateWindow pw = (PrivateWindow) e.nextElement();
  	    	pw.setSelectedBackground(c);
  		}
    }

	public Color getBackground()
	{
		return(mainbg);
    }

    public Color getTextBackground()
	{
		return(textbg);
    }

    public Color getTextForeground()
	{
		return(textfg);
    }

	public void cutPaste(String s)
	{
		if (tac == null)
		{
			tac = new TextAreaCopy(this, getFrame(), s);
			tac.setName("tac");
			tac.addWindowListener(this);
		}
		else
		{
			tac.toFront();
		}
		tac.setText(s);
    }

    public void visitURL(URL url)
	{
		visitURL(url, "_blank");
    }

    public void visitURL(URL url, String target)
	{
		getAppletContext().showDocument(url, target);
    }
	
	/*
	** Javascript interaction
	*/
	public void sendCommand(String text)
	{
		sendCommand(text, getCurrentPanel());
	}

    public void actionPerformed(ActionEvent ev)
	{
   		this.new_nick = nick_entry.getText();
   		if (new_nick.length() == 0)
   	    	return;
	
		if (!isNickCorrect(new_nick))
			return;		

   		if (!connected)
   		{
   	    	connect();
   		}
   		else
   		{
   	    	String p[] = { new_nick };
   	    	sendMessage("NICK", p);
   		}
	}

	public void mouseClicked(MouseEvent ev)
	{
	}

	public void mouseEntered(MouseEvent ev)
	{
		getFrame().setCursor(new Cursor(Cursor.HAND_CURSOR));
	}

	public void mouseExited(MouseEvent ev)
	{
		getFrame().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
	}

	public void mousePressed(MouseEvent ev)
	{
		menu.show(away, ev.getPoint().x, ev.getPoint().y);
	}

	public void mouseReleased(MouseEvent ev)
	{
	}

    public void itemStateChanged(ItemEvent ev)
	{
		String p[] = { "" };

		for (int i = 0; i < menu.getItemCount(); i++)
		{
			MenuItem item = menu.getItem(i);
			if (item instanceof CheckboxMenuItem)
			{
				if(menu.getItem(i) == ev.getSource() && ((CheckboxMenuItem)item).getState())
				{
					if (i == 0)
					{
						p[0] = openAwayWin();
						if (p[0].equals(""))
							((CheckboxMenuItem)item).setState(false);
					}
					else
					{
						p[0] = item.getLabel();
					}
				}
				else
				{
					((CheckboxMenuItem)item).setState(false);
				}
			}
		}

		sendMessage("AWAY", p);
    }

	public void playSound(int i)
	{
		if (silent == res.SND_OFF || (silent == res.SND_OFFAWAY && is_away))
			return;

		if (event_sounds[i] != null)
			event_sounds[i].play();
	}

    public void chatPanelClosing(ChatPanelEvent ev)
	{
		ChatPanel source = ev.getChatPanel();
		String name = source.getPanelTag();

		chat_panel.remove(name);
		if (source instanceof PrivateWindow)
		{
	    	properties.deleteObserver((PrivateWindow) source);
	    	privates.remove(name);
  		}
		else if (source instanceof ChannelWindow)
		{
	    	properties.deleteObserver((ChannelWindow) source);
	    	channel_windows.remove(name);
		}
    }

    public void windowOpened(WindowEvent e)
    {
    }

    public void windowClosing(WindowEvent e)
	{
		String name = e.getWindow().getName();

		if (name.equals("main"))
		{
			f.dispose();
		  	stop();
		  	destroy();
			System.exit(0);
		}
		if (name.equals("tac") && tac != null)
		{
			tac.dispose();
			tac = null;
		}
		if (name.equals("configurator") && configurator != null)
		{
			configurator.dispose();
			configurator = null;
		}
		if (name.equals("asl") && asl != null)
		{
			asl.dispose();
			asl = null;
		}
		if (name.equals("mode_list") && mode_list != null)
		{
			mode_list.dispose();
			mode_list = null;
		}
		if (name.equals("help") && help != null)
		{
			help.dispose();
			help = null;
		}
    }

    public void windowClosed(WindowEvent e)
    {
    }

    public void windowIconified(WindowEvent e)
    {
    }

    public void windowDeiconified(WindowEvent e)
    {
    }

    public void windowActivated(WindowEvent e)
    {
    }

    public void windowDeactivated(WindowEvent e)
    {
    }

    private final static String [][] param_info =
	{
		{ "server", "string", "IRC server's address" },
		{ "port", "0-65535", "IRC server's port" },
		{ "irc_pass", "string", "clients' password to server" },
		{ "mainbg", "color", "general background color" },
		{ "mainfg", "color", "general foreground color" },
		{ "textbg", "color", "text background color" },
		{ "textfg", "color", "text foreground color" },
		{ "selbg", "color", "selected background color" },
		{ "selfg", "color", "selected foreground color" },
		{ "join", "string", "channel(s) to auto-join" },
		{ "username", "string", "username part of hostmask" },
		{ "realname", "string", "user's real name" },
		{ "nickname", "RFC 1459 string", "user's nick name" },
		{ "nicksrv_pass", "string", "nickserv user's password" },
		{ "login", "0/1", "whether to try auto-login" },
		{ "asl", "0/1", "if user is logged out, display a login dialog window" },
		{ "disable_cmds", "string", "list of commands to disable" },
		{ "user_modes", "string", "set or unset user modes after connection" },
		{ "spawn_frame", "0/1", "spawn separate frame" },
		{ "gui_nick", "0/1", "remove or add nick change field" },
		{ "gui_away", "0/1", "remove or add away menu" },
		{ "gui_chanlist", "0/1", "remove or add channels list" },
		{ "gui_userlist", "0/1", "remove or add users list" },
		{ "gui_options", "0/1", "remove or add options" },
		{ "gui_help", "0/1", "remove or add help" },
		{ "gui_connect", "0/1", "remove or add connect button" },
		{ "width", "int", "frame width" },
		{ "height", "int", "frame height" },
		{ "debug_traffic", "0-2", "disable, enable debugging to console or to applet" },
		{ "write_color", "0-15", "messages color" },
		{ "font_name", "string", "font name" },
		{ "font_size", "integer", "font size" },
		{ "language", "string", "preferred language, or leave empty for auto-detect" }
    };

	public class COMClassObject
	{
		/* Documented bug at :
		// http://support.microsoft.com/default.aspx?scid=kb;en-us;243771
		//
		// Empty class prevent MS's JVM
		// to load a non-existant "<applet>$COMClassObject.class".
		// Could save www errors log...
		*/
	}
}
